Unity + HybridCLR 社区版下 UniTask 泛型 AOT 问题排查记录
最近在接入小七 GameSDK 登录流程时,Android 真机上遇到了 HybridCLR + UniTask 的 AOT 泛型缺失问题。这里记录一下问题现象、排查过程和最终处理方案。
问题现象
真机日志里出现类似错误:
AOT generic method not instantiated in aot. assembly: UniTask.dll method: Cysharp.Threading.Tasks.UniTask`1[ System.ValueTuple`2[System.Boolean,GameSDK.LoginResult] ] Cysharp.Threading.Tasks.UniTask::WhenAny<GameSDK.LoginResult>( UniTask<LoginResult>, UniTask )
最开始还出现过:
UniTaskCompletionSource<ValueTuple<bool, string>>::get_Task() TrySetResult(ValueTuple<bool, string>)
这些都属于典型的 HybridCLR AOT 泛型问题。
根因
项目使用的是 HybridCLR 社区版,不是商业版完全泛型共享。
社区版下,如果 AOT 程序集里的泛型方法在 IL2CPP 编译阶段没有生成对应实例,热更层运行时调用就可能报:
AOT generic method not instantiated
这次缺失的方法在日志里已经写得很清楚:
assembly: UniTask.dll UniTask.WhenAny<LoginResult>
所以真正缺的是 UniTask.dll 的补充元数据,而不是热更 DLL 本身。
一开始走过的弯路
我们尝试过在 RefTypes.cs 里补泛型引用,例如:
RefUniTaskCompletionSource(default(LoginResult)); RefUniTaskResult(default(ValueTuple<bool, LoginResult>));
但 LoginResult 在热更程序集里,RefTypes.cs 属于 AOT 侧。直接在 AOT 侧引用热更类型,容易触发 linker 问题,比如之前遇到的 IL1005。
所以最终不建议在 RefTypes 里直接 using GameSDK 或直接引用 LoginResult。
正确方向
根据 HybridCLR 文档,社区版可以通过补充元数据解决 AOT 泛型问题。
这次报错来自:
UniTask.dll
因此需要把 UniTask 加入 AOT 补充元数据列表,并确保运行时加载:
Assets/GameMain/AotDll/UniTask.bytes
实际修改
1. 运行时 AOT 元数据加载列表
在:
Assets/Scripts/Procedure/ProcedureLoadHotFixDll.cs
把 UniTask 加入 aotDllList:
public static List<string> aotDllList = new List<string>
{
"Base",
"GameFramework",
"Google.Protobuf",
"System",
"UniTask",
"UnityGameFramework.Runtime",
"mscorlib",
};运行时会通过:
RuntimeApi.LoadMetadataForAOTAssembly(dllBytes, HomologousImageMode.SuperSet);
加载这些 AOT 元数据。
2. HybridCLR 配置
在:
ProjectSettings/HybridCLRSettings.asset
把 UniTask 加入 patchAOTAssemblies。
否则执行 HybridCLR 的复制流程时,不会自动把 UniTask.dll 拷贝成 UniTask.bytes。
3. 编辑器构建列表
在:
Assets/ThirdParty/HybridCLR/Editor/BuildConfig.cs
也加入:
"UniTask",
保证项目自定义构建 AOT bytes 时也会处理 UniTask。
4. 生成 AOT bytes
项目里 Assets/GameMain/AotDll 的命名规则是:
程序集名.bytes
不是:
程序集名.dll.bytes
所以最终应该存在:
Assets/GameMain/AotDll/UniTask.bytes
来源是:
HybridCLRData/AssembliesPostIl2CppStrip/Android/UniTask.dll
RefTypes 最终怎么处理
RefTypes.cs 保持基础 UniTask 泛型引用即可,不直接引用热更类型:
RefUniTaskCompletionSource(default(bool)); RefUniTaskCompletionSource(default(string)); RefUniTaskCompletionSource(default(object)); RefUniTaskCompletionSource(default(ValueTuple<bool, string>)); RefUniTaskCompletionSource(default(ValueTuple<bool, object>)); RefUniTaskResult(default(object)); RefUniTaskResult(default(ValueTuple<bool, object>));
LoginResult 这种热更类型的泛型问题,交给 UniTask.bytes 补充元数据解决。
登录流程调整
另一个问题是登录流程本身。
最终约定:
initSdk()只做 SDK 初始化不在
initSdk()里触发登录登录只在
SendwxLogin()里触发第一次进入登录界面不自动登录
用户点击登录按钮后再走小七登录
原因是 initSdk() 会被 UI 刷新、协议同意等多处调用。如果把登录放进去,登录失败后刷新 UI 可能导致反复自动登录。
小七登录超时问题
原来登录服务里有 15 秒超时:
var loginTask = _provider.LoginAsync(); var timeoutTask = UniTask.Delay(TimeSpan.FromSeconds(15)); var (hasResult, loginResult) = await UniTask.WhenAny(loginTask, timeoutTask);
这个对小七不合理。
小七登录会弹账号密码输入框,用户可能超过 15 秒才输入。这个场景不应该由客户端 SDK 层强制超时。
最终改为直接等待 SDK 回调:
var loginResult = await _provider.LoginAsync();
小七 Provider 内部也改成:
var loginResult = await _loginTcs.Task;
只有 SDK 返回成功、失败或取消时,登录流程才继续。
内存影响
补充 UniTask.bytes 会增加包体和运行时内存。
当前 UniTask.bytes 大约 512 KB。加载补充元数据后,运行时内存会有额外占用,而且这部分通常不可回收。
如果对内存敏感,可以考虑使用 HybridCLR 提供的补充元数据裁剪工具,优化 AOT metadata dll 大小。
总结
这次问题的关键点是:
报错看
assembly,这次是UniTask.dll社区版要靠补充元数据解决 AOT 泛型
UniTask必须加入 AOT 元数据列表不建议在
RefTypes里直接引用热更类型initSdk()只初始化,登录放在用户触发的登录入口小七账号密码登录不能加固定 15 秒超时
最终方案是:
业务代码正常写 UniTask 泛型 + 运行时加载 UniTask.bytes 补充元数据 + RefTypes 只保留基础泛型引用
这套更符合 HybridCLR 社区版的使用方式。