当前位置:首页 > 编程开发

Unity + HybridCLR 社区版下 UniTask 泛型 AOT 问题排查记录

webgou2个月前 (07-03)编程开发143

最近在接入小七 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 大小。

总结

这次问题的关键点是:

  1. 报错看 assembly,这次是 UniTask.dll

  2. 社区版要靠补充元数据解决 AOT 泛型

  3. UniTask 必须加入 AOT 元数据列表

  4. 不建议在 RefTypes 里直接引用热更类型

  5. initSdk() 只初始化,登录放在用户触发的登录入口

  6. 小七账号密码登录不能加固定 15 秒超时

最终方案是:

业务代码正常写 UniTask 泛型
+
运行时加载 UniTask.bytes 补充元数据
+
RefTypes 只保留基础泛型引用

这套更符合 HybridCLR 社区版的使用方式。


扫描二维码推送至手机访问。

版权声明:本文由知了博客发布,如需转载请注明出处。

本文链接:https://www.webgou.info/?id=702

标签: unityAI
分享给朋友:

“Unity + HybridCLR 社区版下 UniTask 泛型 AOT 问题排查记录” 的相关文章

Flex 中可以应用于 ActionScript 类的元标签

来源:互联网 Flex 开发工作中, 组件的开发是比不可少的环节, 在你的自定义组件中合理地应用元标签可以使组件更容易, 更方便, 更友好地被引入到应用中. 你可以为MXML和ActionScript文件插入元标签,以便为Flex编译器提供信息。可执行代码中的元标签不会被编译,但会为控制如何编译元标…

实用Android开发工具和资源精选

\ 本文介绍了20个关于Android应用程序开发的实用工具资源,供大家分享。     在google、开源平台,和来自移动电话制造商HTC,Samsung和Sony Ericsson的支持下,Android平台在市场占有率上相比去年取得的886%增长!如果我只看增长率…

十分有趣的N多小网站

十分有趣的N多小网站 给大家参考下…

c++操作符重载

c++有许多内置数据类型,包括int,char,double, 第一种类型有许多运算符,比如,+ - * /.当用户定义了许多类对象时,对像间是不能进行这些操作的.但c++类允许把这些运算符内置到c++类中,可以像操作内置对象一样操作.…

线程

线程是由两个部分组成的: * 一个是线程的内核对象,操作系统用它来对线程实施管理。内核对象也是系统用来存放 线程统计信息的地方。 * 另一个是线程堆栈,它用于维护线程在执行代码时需要的所有函数参数和局部变量 进程是不活泼的。进程从来不执行任何东西,它只是线程的…

发表评论

访客

看不清,换一张

◎欢迎参与讨论,请在这里发表您的看法和观点。