Unity AudioMixer 改为 AssetBundle 加载后 class ID 245 崩溃
记录 Unity 项目中 AudioMixer 从场景静态引用改为脚本 AssetBundle 加载后,在 Android IL2CPP 包上触发 Could not produce class with ID 245 的定位过程和 link.xml 修复方式。
问题背景
这次崩溃出现在 Unity 音频系统切换过程中。
最开始的实现方式是:在场景里静态挂载一个 UnityAudioMixer 引用。这个方式下,AudioMixer 资产会被场景直接引用,打包主工程时 Unity 能看到这条依赖链,Android 包运行正常。
后来为了把音频初始化收敛到统一入口,新增了 AudioAPIMgr,并把 mixer 改成运行时脚本加载:
1
2
3
4
5
6
7
8
9
10
private const string AudioMixerPath = "Audio/setting/AudioMixer";
public void Init()
{
_audioRoot = new GameObject("UnityAudio");
_audioMixer = ResMgr.Instance.LoadAudioMixer(AudioMixerPath);
var pool = new AudioSourcePool(_audioRoot.transform);
var config = LoadConfig();
// ...
}
也就是说,AudioMixer 不再由场景静态引用,而是进入 AssetBundle 后,由 ResMgr -> AssetBundleManager -> AssetBundle.LoadAsset 在运行时加载。
崩溃日志
Android 进游戏初始化 UI 时崩溃,日志核心如下:
1
2
3
4
5
6
7
8
9
10
11
12
E Could not produce class with ID 245.
This could be caused by a class being stripped from the build even though it is needed.
Try disabling 'Strip Engine Code' in Player Settings.
UnityEngine.AssetBundle:LoadAsset(String)
GameEngine.Resource.AssetBundleManager:GetAsset(String, Boolean)
GameEngine.Manager.AudioAPIMgr:Init()
XLua.CSObjectWrap.GameEngineManagerAudioAPIMgrWrap:_m_Init(IntPtr)
XLua.LuaDLL.Lua:lua_pcall(IntPtr, Int32, Int32, Int32)
XLua.LuaFunction:Call(Object[], Type[])
GameEngine.Manager.FUIController:OnInit()
FairyGUI.Window:Init()
第一眼容易怀疑是 AudioAPIMgr.LoadConfig() 或新增的 AudioLabelConfig 被裁剪。因为崩溃点刚好发生在 AudioAPIMgr.Init(),并且配置也刚迁进了新音频系统。
实际不是这个方向。
定位过程
AudioAPIMgr.Init() 里有两个明显的加载点:
1
2
_audioMixer = ResMgr.Instance.LoadAudioMixer(AudioMixerPath);
var config = LoadConfig();
其中 LoadConfig() 走的是:
1
2
3
4
5
6
private AudioLabelConfig LoadConfig()
{
var confBundle = ResMgr.GetConfBundle();
var content = confBundle.LoadAsset<TextAsset>(AudioConfigName);
// ...
}
如果是 AudioLabelConfig 或 audiolabelconfig.txt 的问题,加载类型应该是 TextAsset,Unity class ID 对应的是文本资产,而不是日志里的 245。
继续查看 AudioMixer.mixer 的 YAML,可以看到:
1
2
3
4
5
6
7
8
9
10
--- !u!241 &24100000
AudioMixerController:
m_Name: AudioMixer
m_Snapshots:
- {fileID: 24500006}
m_StartSnapshot: {fileID: 24500006}
--- !u!245 &24500006
AudioMixerSnapshotController:
m_Name: Snapshot
再看 AssetBundle manifest,对应 bundle 里也包含:
1
2
3
4
5
6
ClassTypes:
- Class: 241
- Class: 243
- Class: 245
Assets:
- Assets/BundleResources/Audio/setting/AudioMixer.mixer
因此 class ID 245 对应的是 AudioMixerSnapshotController。这说明崩溃点不是 AudioLabelConfig,而是运行时从 AssetBundle 反序列化 AudioMixer.mixer 时,播放器里缺少了相关 AudioMixer 类型支持。
为什么场景静态引用没问题
场景静态挂载 UnityAudioMixer 引用时,Unity 构建主包时可以直接扫描到 AudioMixer 资产依赖。AudioMixer、AudioMixerGroup、AudioMixerSnapshot 相关类型会自然进入构建引用链。
改成脚本加载后,主包里只有字符串路径:
1
"Audio/setting/AudioMixer"
UnityLinker 和 Strip Engine Code 都无法仅凭字符串判断运行时一定会加载 AudioMixer。最终表现就是:资源在 AssetBundle 里存在,但播放器运行时反序列化到 AudioMixerSnapshotController 时,发现相关类型没有被保住,于是报:
1
Could not produce class with ID 245
误区:UnityEngine.Audio 不是类型
一开始尝试在 link.xml 增加:
1
<type fullname="UnityEngine.Audio" preserve="all"/>
这条配置没有解决问题,因为 UnityEngine.Audio 是命名空间,不是具体类型。
link.xml 的 <type fullname="..." /> 需要写真实类型名,或者直接 preserve 对应程序集。
最终修复
在 link.xml 里 preserve UnityEngine.AudioModule 中的 AudioMixer 相关类型:
1
2
3
4
5
6
7
<assembly fullname="UnityEngine.AudioModule">
<type fullname="UnityEngine.Audio.AudioMixer" preserve="all"/>
<type fullname="UnityEngine.Audio.AudioMixerGroup" preserve="all"/>
<type fullname="UnityEngine.Audio.AudioMixerSnapshot" preserve="all"/>
<type fullname="UnityEngine.AudioSource" preserve="all"/>
<type fullname="UnityEngine.AudioClip" preserve="all"/>
</assembly>
或者更粗粒度地保整个 AudioModule:
1
<assembly fullname="UnityEngine.AudioModule" preserve="all"/>
这次项目使用上述 AudioModule preserve 后,Android 包可以正常进游戏,不再在 AudioAPIMgr.Init() 阶段崩溃。
结论
这次问题的根因不是 AssetBundle 文件损坏,也不是 AudioLabelConfig JSON 加载失败,而是引用方式变化导致构建期依赖可见性改变:
- 旧方案:场景静态引用
AudioMixer,Unity 构建时能看到依赖。 - 新方案:
AudioAPIMgr运行时通过字符串路径从 AssetBundle 加载AudioMixer。 - 结果:
AudioMixerSnapshotController对应的 class ID 245 没有被保住,运行时反序列化 AssetBundle 崩溃。 - 修复:在
link.xml中 preserveUnityEngine.AudioModule或至少 preserveAudioMixer、AudioMixerGroup、AudioMixerSnapshot。
后续如果类似资源从场景静态引用改为 AssetBundle 字符串加载,需要额外检查两类内容:
- 资源本身是否在正确平台的 AssetBundle 中。
- 该资源反序列化依赖的 Unity 模块类型是否会被裁剪。