Unity IL2CPP 热更新解决方案完整指南
HybridCLR(原 Huatuo)是 Unity IL2CPP 的扩展方案,在保留 IL2CPP 性能优势的同时,增加了代码热更新能力。
| 代码类型 | 能否热更新 | 说明 |
|---|---|---|
| AOT 代码 | ❌ 不能 | 已编译成机器码,嵌入游戏包 |
| 热更新代码 | ✅ 可以 | IL 格式,可动态加载替换 |
将热更新程序集的类型信息注册到 IL2CPP 的元数据系统,使 AOT 代码可以通过反射等方式调用热更新代码。
| 特性 | HybridCLR | ILRuntime | Lua/xLua |
|---|---|---|---|
| 语言 | C# | C# | Lua |
| 执行方式 | IL 解释 | IL 解释 | Lua 虚拟机 |
| 性能 | 较高 | 中等 | 中等 |
| 开发体验 | 原生 C# | 需要适配 | 需要学 Lua |
| 与 Unity 兼容 | 完全兼容 | 部分限制 | 需要绑定 |
DLL = Dynamic Link Library(动态链接库)
DLL 是把代码打包成一个文件,供其他程序调用使用的"代码包"。
| 语言 | 效率 | 原因 |
|---|---|---|
| C | ⭐⭐⭐⭐⭐ | 零开销抽象,直接映射硬件 |
| C++ | ⭐⭐⭐⭐⭐ | 同 C,编译器优化成熟 |
| Rust | ⭐⭐⭐⭐⭐ | 零成本抽象,LLVM 优化 |
| Go | ⭐⭐⭐⭐ | 有 GC、运行时开销 |
问题:AOT 泛型限制
IL2CPP 在 AOT 编译时,只会为"用到的"泛型组合生成代码:
List<HotClass> 就会出问题!
private static readonly string[] AOTAssemblies = new string[]
{
"mscorlib.dll",
"System.dll",
"System.Core.dll",
"UnityEngine.CoreModule.dll",
// ... 其他需要的程序集
};
void LoadMetadataForAOTAssemblies()
{
foreach (var aotDll in AOTAssemblies)
{
byte[] dllBytes = LoadDllBytes(aotDll);
LoadImageErrorCode err = RuntimeApi.LoadMetadataForAOTAssembly(
dllBytes,
HomologousImageMode.SuperSet
);
Debug.Log($"LoadMetadataForAOTAssembly {aotDll}: {err}");
}
}
| 特性 | 说明 |
|---|---|
| 一次补充,永久适用 | 泛型定义是 List<T>,T 可以是任何类型 |
| 可分批加载 | 只要在使用前加载即可 |
| 值类型也支持 | IL 是参数化的,运行时确定类型大小 |
| 执行方式 | HybridCLR 解释执行 IL(比机器码慢 3-10 倍) |
HybridCLR → Generate → AOTGenericReferences
Unity IL2CPP 构建时会进行代码裁剪(Stripping),移除"未使用"的代码以减小包体。
但有些代码是动态调用的,编译器检测不到:
// 这些调用方式,编译器检测不到!
Type type = Type.GetType("MyNamespace.MyClass"); // 反射
JsonUtility.FromJson<MyClass>(jsonStr); // 序列化
<linker>
<!-- 保留整个程序集的所有内容 -->
<assembly fullname="mscorlib" preserve="all"/>
<!-- 保留程序集中的特定类型 -->
<assembly fullname="UnityEngine.CoreModule">
<type fullname="UnityEngine.Vector3" preserve="all"/>
<type fullname="UnityEngine.Quaternion" preserve="all"/>
</assembly>
<!-- 保留命名空间下的所有类型 -->
<assembly fullname="Assembly-CSharp">
<namespace fullname="MyGame.Data" preserve="all"/>
</assembly>
</linker>
| 特性 | link.xml | 补充元数据 |
|---|---|---|
| 保留的是 | 机器码(IL 被转换丢弃) | IL 字节码 |
| 执行方式 | CPU 直接执行 | HybridCLR 解释执行 |
| 解决的问题 | 防止代码被删除 | 提供泛型 IL 定义 |
| 作用时机 | 构建时 | 运行时 |
热更新代码可以直接调用 AOT 代码,就像普通 C# 代码一样:
// 热更新代码中
using UnityEngine; // Unity API (AOT)
using GameCore; // 游戏核心框架 (AOT)
namespace HotUpdate
{
public class HotLogic
{
public void Start()
{
// ✅ 调用 Unity API(AOT 机器码)
Debug.Log("Hello from HotUpdate!");
// ✅ 调用游戏核心框架(AOT)
GameManager.DoSomething();
// ✅ 使用 .NET 类型(AOT)
var list = new List<string>();
}
}
}
引用类型的泛型可以共享机器码:
autoReferenced: false
// HotUpdate.asmdef
{
"name": "HotUpdate",
"references": [
"GameCore"
],
"autoReferenced": false,
"includePlatforms": [],
"excludePlatforms": []
}
↑ autoReferenced: false 禁止被 Assembly-CSharp 自动引用
// 推荐:启动时加载常用的 AOT 程序集
void LoadMetadata()
{
// 必须加载
LoadMetadataForAOTAssembly("mscorlib.dll");
LoadMetadataForAOTAssembly("System.Core.dll");
// 按需加载
LoadMetadataForAOTAssembly("UnityEngine.CoreModule.dll");
LoadMetadataForAOTAssembly("UnityEngine.PhysicsModule.dll");
LoadMetadataForAOTAssembly("UnityEngine.UI.dll");
}
| 代码位置 | 执行方式 | 性能 | 建议 |
|---|---|---|---|
| AOT | 机器码 | ⚡ 最快 | 性能关键代码 |
| 热更新(已有泛型实例) | 机器码共享 | ⚡ 快 | 大部分代码 |
| 热更新(新泛型组合) | IL 解释 | 🐢 慢 3-10 倍 | 非性能关键代码 |
A: 可以,直接引用即可,像普通 C# 代码一样。
A: 不能直接调用(编译时热更新代码不存在),但可以通过接口/委托/反射间接调用。
A: 技术上可以,但通常不需要,因为它们随 Unity 版本固定,基本不会变。
A: 可以,补充元数据提供的 IL 是参数化的,HybridCLR 解释器会在运行时处理任意值类型。
A: 比 AOT 机器码慢 3-10 倍,但对于业务逻辑通常可以接受。