HybridCLR核心原理与Unity热更新实战:从IL2CPP到混合执行
2026/9/15 11:31:38 网站建设 项目流程

简介:面向 Unity 开发者的 HybridCLR 全平台 C# 热更新解决方案源码包,适用于需要不重新下载安装包、随时动态更新游戏内容与修复线上 Bug 的客户端团队,主打零成本接入、高性能低内存占用,原生支持动态加载 assembly、多线程与 Unity 标准工作流,可明显降低热更引入的维护和性能代价。资源共 92 个文件,以 41 个头文件与 38 个 cpp 源文件为主体,涵盖 HybridCLR Runtime、Interpreter、metadata transform、Il2CppCompatibleDef 等核心模块;另有 3 个 Markdown 说明文档、2 个 yml 工程配置、说明文件 txt 与附赠资源 docx,便于快速理解项目结构、API 用法和集成步骤。压缩包仅 442KB,轻量且层次清晰,既包含 hybridclr-main 主仓库代码,也包含 docs、sponsor、images 等配套目录,方便按需查阅。目前已有 137 人学习下载,说明该方案在实际技术选型中受到一定关注。通过该源码包可系统学习基于 IL2CPP 的 C# 热更新实现原理,掌握动态加载 assembly 与多线程更新机制,也可基于 RuntimeApi、CommonDef 等代码做二次开发和问题排查,适合中高级 Unity 工程师深入实践与定制扩展。

1. HybridCLR 到底在解决 Unity 热更新的什么问题

Unity 圈子里流传着一个有点反直觉的结论:在 IL2CPP 时代,C# 本身是“不能热更新”的,因为所有代码被提前编译成 C++ 再变成原生指令,运行时无法再塞进新的托管方法。于是 Lua、ILRuntime 这类方案长期霸占热更新赛道,代价是语言割裂、调试困难、性能损耗和额外的内存开销。HybridCLR 的核心做法是用“AOT + Interpreter 混合执行”把这条死路重新打开:热更代码不是被解释执行的独立语言,而是元数据补全后的原生 C# IL 指令。因此它能复用 Unity 的脚本生命周期、支持多线程、直接加载外部 assembly,同时保持接近原生的执行性能和极低的内存增量。

这篇文章面向两类读者:一类被 Lua 热更折磨过,想换回纯 C# 的团队;另一类是评估热更方案、需要知道 HybridCLR 边界的技术负责人。我会先讲清楚为什么它敢称“零成本高性能”,再给出可复现的集成步骤、动态加载程序集和多线程的落地写法,最后聊几个不翻文档根本发现不了的坑。你不需要提前看过项目源码,跟着命令和参数走就能跑通。

2. 为什么 HybridCLR 能做到高性能低内存:从 IL2CPP 到混合执行

2.1 IL2CPP 的“死代码”与 HybridCLR 的元数据补全

要理解 HybridCLR,得先回到 IL2CPP 的编译模型。常规 Unity 打包时,C# 代码会被编译成 IL,再由 IL2CPP 转成 C++,最终编译为各平台的原生二进制。这个过程里,类型定义、方法签名、字符串字面量等信息会序列化到global-metadata.dat文件中,而实际函数变成原生指令后,原始 IL 字节码并不会被完整保留。Unity 官方给出的热更新限制就来自这里:运行时只能调用“已经存在”的原生方法,动态加载一个新程序集时,其中引用的类型和成员如果在 AOT 侧没有对应元数据,就会直接抛MissingMethodExceptionTypeLoadException

HybridCLR 的做法是在 IL2CPP 基础上做了一层“元数据补全”。它在打包时把 AOT 程序集的 IL 元数据同时保留下来,运行时如果遇到未提前编译的泛型实例化或新加载的程序集,就不再走 AOT 的原生调用,而是切换到内置的解释器执行对应的 IL 字节码。这套机制有点像给 IL2CPP 加了一个“补丁引擎”,但补丁内容不是别人定义的语言,而是 C# 自己。你可以把 HotUpdate 程序集里的方法当成普通 C# 方法写,只是运行时一部分走原生、一部分走解释,调用边界由 HybridCLR 的桥接层决定。

这里的关键点是:它并不是把整个项目都解释执行,而是只有那些“新加入的、AOT 里没有的”代码才落到解释器。一个典型项目里,框架层、第三方库、常用泛型容器往往都在 AOT 侧,热更程序集里真正新增的代码量占比很小,因此整体运行路径大部分仍然是原生指令。这就是它敢标称“高性能”的结构基础。

2.2 与 Lua、ILRuntime 的本质区别

很多团队选热更方案时会拿 HybridCLR 和 Lua、ILRuntime 放在同一张表里比,但它们的优化目标差异很大。Lua 是嵌入一门新语言,ILRuntime 是在 C# 上重建一个解释器,而 HybridCLR 复用的是 C#/IL 自身,只是在 AOT 与 Interpreter 之间做切换。区别可以落到四个维度:类型系统、泛型支持、多线程模型和调试体验。

维度Lua(xlua/toLua)ILRuntimeHybridCLR
语言Lua,与 C# 类型边界需要适配层C# IL 解释执行,但泛型支持受限C# IL,与 Unity 原生 C# 类型完全一致
泛型不支持真正的 C# 泛型,需要反射或 workaround泛型方法/类型支持有限,跨域传参需要映射AOT 侧泛型 + 解释器补充,行为与原生一致
多线程Lua 自身非线程安全,跨线程调用复杂解释器带锁,支持有限解释器内部保证线程安全,无额外调度限制
调试Lua 调试器,调用栈割裂可调试,但需要额外工具Unity 标准 C# 调试,断点、Step、变量检查均可用
内存占用Lua 虚拟机 + 对象池,额外开销明显IL 指令解析 + 跨域对象包装解释器共享 AOT 元数据,仅在热更类型上增加少量解释状态

从实际表现看,Lua 方案的最大问题不是运行速度,而是“两套语言导致的心智负担”,开发一个功能要么写纯 Lua,要么写 C# 给 Lua 调,边界处大量使用XLuaApi或反射,遇到性能问题还得用 C# 重写热点。ILRuntime 的定位是“能跑的 C# 热更”,但泛型和值类型处理一直有坑,尤其结构体在跨域传参时的装箱拆箱非常难受。HybridCLR 因为保留了完整的 IL,泛型实例化、值类型布局、接口派发这些行为都能对齐 AOT C#,所以热更代码里可以放心使用List<T>Dictionary<TKey, TValue>async/await甚至unsafe,这在其他方案里都是高危操作。

2.3 性能与内存的量化依据

“零成本”当然不是字面意思。解释执行的指令天然比原生指令慢,但关键在于慢的比例和被覆盖的代码量。HybridCLR 社区给的参考数据是解释器指令耗时大约是同逻辑原生代码的 1.5 到 3 倍,听起来不低,但实际游戏逻辑里大量时间是花在Update、物理、渲染和资源加载上,纯 C# 业务逻辑的 CPU 占比通常不到 10%。就算整个业务逻辑都在解释器里跑,帧率下降也只有几个百分点;更常见的情况是只有热更模块在解释态,表现上几乎无感。

内存方面,HybridCLR 不会为每个热更对象创建额外的代理包装,也没有 Lua 表或 ILRuntime 那种跨域对象句柄。解释器的成本主要是:每个解释执行的方法栈帧。栈帧里包含局部变量表、操作数栈和当前指令指针,在 x64 平台上大概占几百字节,但栈帧是复用的,方法调用结束后立刻归还。真正占内存的是“解释器类型额外的运行时元数据”,这部分只在新建热更类型时分配,通常每个类型几 KB,相对整个项目的资源包体量可以忽略。

我一般会用 Unity Profiler 做验证:在热更新模块里压一段复杂的字符串解析或 JSON 反序列化,对比它在纯编译进 AOT 时的耗时和内存,差距如果大于 2 倍,就要检查是不是热更代码里频繁用了反射或 LINQ。更简单的办法是看 Profiler 里HybridCLR.Interpreter相关的采样耗时,如果超过总耗时的 5%,就该考虑把热点函数挪到 AOT 侧或改为批量处理。

3. 30 分钟集成 HybridCLR:完整初始化与最小热更新流程

3.1 环境要求与包安装

HybridCLR 对 Unity 版本有明确要求,它依赖 IL2CPP 的运行时元数据接口,所以只能用于支持 IL2CPP 的 Unity 版本(Unity 2019.4 到 2022 LTS 都有对应分支)。安装有两种思路:直接用 Unity Package Manager 添加 git 地址,或者手动下载hybridclr_unity包放到Packages目录。社区里普遍建议用 UPM 方式,切换版本时只用改一次manifest.json

{ "dependencies": { "com.code-philosophy.hybridclr": "https://github.com/focus-creative-games/hybridclr_unity.git#2.4.2" } }

参数说明:#2.4.2表示拉取对应 tag,不写则默认使用仓库最新 commit。实际项目里必须锁定版本,否则升级 Unity 或更换分支后 API 变化会让你无从排查。安装完成后,菜单栏会出现HybridCLR选项,首先要执行HybridCLR -> Installer,这一步会把运行时库和桥接代码写入项目的Assets目录。

接下来需要配置工程里的 IL2CPP 设置。打开Project Settings -> Player -> Other Settings,将Scripting Backend设为IL2CPPApi Compatibility Level设为.NET Standard 2.1.NET Framework,视 Unity 版本而定。注意Allow 'unsafe' Code选项不要勾选,HybridCLR 的解释器模块不需要它,而热更代码如果用了unsafe反而会给打包带来额外的元数据处理。

3.2 配置 global-metadata 与打包参数

安装完成后,必须生成桥接函数和补充元数据,否则打包出来的包会在运行时崩溃。菜单栏执行HybridCLR -> Generate -> All,这条命令会完成三件事:

  1. 生成AOT泛型实例化需要的补充代码;
  2. 生成解释器与 AOT 之间的桥接函数;
  3. 生成link.xml,防止热更程序集在代码裁剪时被删掉。

如果你漏掉了这一步,最常见的报错是FileNotFoundException: Could not load file or assembly 'hotupdate.dll' or one of its dependencies,或者直接是ExecutionEngineException。生成完成后,重新检查Assets/AssetBundles下的global-metadata.dat,HybridCLR 已经把它替换成了包含额外元数据的版本。这个文件在打包后会被加密或压缩,但运行时读取逻辑已经由 HybridCLR 的Loader模块接管,你不需要手动处理。

打包参数注意点:热更程序集必须放到单独的Assembly Definition或独立的.asmdef中,并且不要挂在Assembly-CSharp下。我的做法是新建HotUpdate目录,配套HotUpdate.asmdef,然后把所有热更代码放在这个 asmdef 中。打包时要将该 asmdef 链接到主工程之外的 DLL 生成目录,这样主程序集不会引用它,从根源上避免循环依赖。

3.3 一个能跑的热更新 C# 文件实例

初始化 Host 侧的 HybridCLR 运行时只需要几行 C# 代码。先准备好主工程入口,在场景启动时加载热更程序集:

using System; using System.Collections; using System.IO; using System.Reflection; using UnityEngine; public class Bootstrap : MonoBehaviour { private IEnumerator Start() { // 从持久化目录读取热更 dll,这里用 www 或 UnityWebRequest 都行 byte[] dllBytes = File.ReadAllBytes(Path.Combine(Application.persistentDataPath, "hotupdate.dll")); byte[] pdbBytes = File.ReadAllBytes(Path.Combine(Application.persistentDataPath, "hotupdate.pdb")); // 加载程序集,注意使用 Assembly.Load 而不是 Assembly.LoadFrom Assembly assembly = Assembly.Load(dllBytes, pdbBytes); // 调用热更逻辑中的入口方法 Type entryType = assembly.GetType("HotUpdate.Entry"); var entry = Activator.CreateInstance(entryType); MethodInfo startMethod = entryType.GetMethod("Start"); startMethod.Invoke(entry, null); yield return null; } }

代码逻辑说明:Assembly.Load(byte[], byte[])是标准 .NET 程序集加载方式,第二个参数传入 PDB 是为了能在热更程序集里打断点调试。GetType("HotUpdate.Entry")要求热更程序集的命名空间与类型名严格匹配,实践中我会在热更侧暴露一个统一的入口接口,避免反射调用时写错字符串找不到方法。

相应的热更侧代码是:

using UnityEngine; namespace HotUpdate { public class Entry { public static void Start() { Debug.Log("HybridCLR hot update running!"); } } }

如果你把上面这段代码放进主程序集而忘了单独分离 asmdef,打包时 Unity 会正常编译,但运行时因为 AOT 元数据不完整,会出现TypeLoadException: Could not resolve type。记住一个原则:凡是会被热更的程序集,永远不要与主工程 Assembly-CSharp 混在一起。

4. 动态加载 assembly、多线程与 Unity 工作流的兼容边界

4.1 程序集动态加载的代码路径

HybridCLR 支持两种加载方式:一次性加载整个 DLL,或者按依赖树逐个加载。常见做法是事先约定依赖顺序,用Assembly.Load从字节数组加载主热更 DLL,再加载其依赖项。但真实项目中热更 DLL 往往不止一个,比如逻辑层、 UI 层、配置表模块可能拆成三个 dll,这时就要处理依赖。

public static Assembly LoadAssembly(byte[] dll, byte[] pdb = null) { // 先尝试直接加载 try { return Assembly.Load(dll, pdb); } catch (FileNotFoundException ex) { // 如果缺依赖,需要先递归加载依赖程序集 Debug.LogWarning($"Missing dependency: {ex.FileName}"); // 这里根据你的依赖清单逐个加载 // 依赖程序集必须已经放在持久化目录 LoadDependencies(ex.FileName); return Assembly.Load(dll, pdb); } }

参数说明:Assembly.Loadpdb参数可空,但建议带上,否则调试热更代码时看不到符号。ex.FileName是缺失的程序集名,你要维护一张“依赖名 -> 字节数组”的映射表,常见的做法是热更包里的 manifest 文件记录每个 DLL 的 CRC 和依赖关系。

真正的坑不在加载,而在卸载。.NET 默认不支持程序集卸载,除非你把热更程序集放进单独的AssemblyLoadContext,但 Unity 的 IL2CPP 运行时目前不支持可收集的 ALC,所以 HybridCLR 热更程序集一旦加载就无法从内存中移除。因此在设计热更包时,尽量把会频繁更新的模块做小,更新时只替换字节数组而不是反复加载同一个程序集。否则每次热更都会增加一份内存,无法回收。

4.2 多线程环境下需要注意的问题

HybridCLR 官方说明解释器是线程安全的,这点和 Lua 完全不同。实际用起来,确实可以在热更代码里直接开Thread,也可以把Task.Run用在一个热更方法里。但线程安全不等于“每个方法都能任意并发”,解释器内部的元数据查找、泛型实例化在首次触发时是需要加锁的,虽然有锁就有锁竞争,但只是毫秒级以内的延迟。

这里需要给出一个可复现的测试案例。热更侧代码:

using System; using System.Threading; using System.Threading.Tasks; using UnityEngine; public class ThreadStress { private int count = 0; private readonly object lockObj = new object(); public async Task RunAsync() { var t1 = Task.Run(() => Add()); var t2 = Task.Run(() => Add()); await Task.WhenAll(t1, t2); Debug.Log($"Final count: {count}"); } private void Add() { for (int i = 0; i < 100000; i++) { lock (lockObj) { count++; } } } }

代码逻辑说明:这里故意加了lock是为了验证两个线程同时进入解释器执行时锁语义是否正确。如果解释器内部没有正确的同步,count 最终不会是 200000,或者会出现死锁。实际测试中,只要你的代码遵循 C# 的锁规则,结果就是正确的。但注意不要在热更代码里频繁使用Thread.Sleep,解释器线程和 Unity 主线程如果同时等待,会增大 Profiler 里Interpreter.Wait的耗时。

多线程最常见的坑不是锁,而是访问 Unity 对象时的线程限制。热更代码里同样遵守 Unity 的线程模型:只有主线程可以调用UnityEngine.Object的 API,如果热更线程里写transform.position = ...,一样会报MissingReferenceException或直接崩溃。社区不少人误以为 HybridCLR 支持多线程就意味着可以跨线程访问 UnityEngine 对象,这是错的。它只是保证解释器内部状态安全,Unity 引擎自身的线程约束仍然有效。

4.3 与 Unity 原生工作流的兼容边界

HybridCLR 最让人舒服的是完全兼容 Unity 的编辑器工作流。热更代码中的 MonoBehaviour 可以挂到场景物体上,Inspector 序列化的字段也能正常显示,前提是你把这些热更代码所在的程序集标记为热更程序集,并且在编辑器模式下用HybridCLR -> RuntimeApi -> LoadEditorAssembly让 IDE 可以找到类型。运行时加载后,Unity 不会立即识别新的热更类型,所以动态加载完 DLL 后要执行一次Resources.UnloadUnusedAssets,并重新遍历需要挂载热更组件的预制体。

这里有个实际经验:与 3D 或 UI 打交道的热更代码,尽量直接用Resources加载或使用地址系统,不要用AssetDatabaseAssetDatabase只在编辑器下可用,打包后在设备上会抛异常。HybridCLR 不会帮你处理资源加载方式,所以要拿 YooAsset 或 Addressables 来配合资源热更。注意,如果你用了 YooAsset 的加密或混淆插件,需要保证热更 DLL 的加载顺序在资源解密之后,否则会报FileNotFoundException: Could not load file or assembly 'hotupdate.dll',这个我们最后一章细说。

工作流兼容性还表现在:热更代码可以引用主工程程序集,也可以被主工程通过反射调用。但反过来,主工程程序集不能直接静态引用热更程序集,因为它没有被编译进 AOT。我一般会在主工程定义接口,热更侧实现接口,然后通过Activator.CreateInstance创建实例并转成接口。这样既能回到强类型调用,又不打破编译依赖。

5. 热更新验证、常见坑与参数调优

5.1 验证热更是否真正生效

很多团队从 Lua 切到 HybridCLR 后,第一个问题是怎么确认“现在跑的是热更代码”。最直接的方法是打一个包,在包内记录全局标识,然后热更一次。先写一个测试接口:

public interface IVersion { string CurrentVersion(); }

热更程序集里实现:

public class VersionImpl : IVersion { public string CurrentVersion() => "hotfix_v2"; }

主工程里加载后调用:

var type = assembly.GetType("HotUpdate.VersionImpl"); var impl = (IVersion)Activator.CreateInstance(type); Debug.Log(impl.CurrentVersion());

如果设备的日志输出hotfix_v2,那就说明热更已经走通。更严谨的验证方式是看 Profiler 中的解释器调用栈:在CurrentVersion方法里打断点,如果命中了,而且调用栈最底层有HybridCLR.Interpreter帧,就能确认它走的是解释器路径。

5.2 常见报错与关键参数检查表

下面这些是我实际接入过程中遇到频率最高的报错和排查方向:

现象直接原因检查项
FileNotFoundException: Could not load file or assembly 'xxx.dll'依赖缺失或加载顺序错误热更依赖是否先加载;路径是否区分大小写;dll 是否被压缩
ExecutionEngineException: Attempting to call method ...桥接函数未生成重新执行HybridCLR -> Generate -> All后重新打包
TypeLoadExceptionAOT 元数据缺失确认热更 asmdef 已分离;泛型是否被 link.xml 裁剪
MissingMethodException热更代码里调用了 AOT 侧没有的方法检查是否在热更侧用了async/await之外的封闭方法
Profiler 中解释器耗时 > 10%热更代码太厚或频繁反射将热点函数下沉到 AOT,或者换成非反射调用

一个容易忽略的参数是HybridCLR/PlayerSettings里的AOT Generic配置。如果你的热更代码里大量使用List<string>Dictionary<int, object>等泛型容器,需要在打包前把这些泛型实例补充进 AOT 元数据,否则运行时首次创建这些类型时会触发一次较慢的解释器初始化。更稳妥的做法是用HybridCLR.Editor.AOTConsts里的AOTAssemblyList把主工程程序集全部标记为 AOT,这样大部分泛型实例都会被提前生成。

5.3 带宽优先的瘦身策略

热更包通常以 zip 为单位分发,所以热更 DLL 的体积直接影响用户下载流量。HybridCLR 的 DLL 里包含元数据和中间表示,比普通 IL2CPP 的 DLL 要大一些,但可以通过以下方式瘦身:

  1. 剔除调试符号pdb,发布包只上传 dll;
  2. 使用HybridCLR -> CompileDll/ActiveBuildTarget时选 Release 配置,而不是 Debug;
  3. 热更 asmdef 里做link.xml裁剪,把未被热更引用的主工程方法排除;
  4. 多 dll 场景下,把公共依赖合并成一个core.dll,避免重复引用。

裁剪时要小心:link.xml一旦把类型裁剪掉,运行时再想通过Type.GetType反射查找就会失败。所以裁剪规则必须是“白名单”,而不是黑名单。我的做法是先用HybridCLR/Generate/LinkXml生成全量引用,再手动删除确定的废弃类,不要从空文件开始写。

6. 热更新 + 资源管理:给 HybridCLR 加一套 YooAsset 的补全方案

6.1 为什么要配合资源热更

HybridCLR 解决的是代码热更,但 Unity 项目不可能只改代码,美术资源、配置表、UI 预制体同样要热更。如果代码热更后要发整包,那热更的意义就没了一半。社区里常见的组合是 HybridCLR 负责 dll,YooAsset 负责 AssetBundle 的依赖管理和更新。这两套东西叠加时,最容易出问题的是加载时机:YooAsset 更新完资源包后,热更 DLL 是作为一个 AssetBundle 里的文件存在,还是单独下载?

通常做法是把热更 dll 和 pdb 打进 AssetBundle 或直接放在远端目录,然后用 YooAsset 的下载流程拿到字节数组。这个流程和纯代码热更的差别在于,你不再直接File.ReadAllBytes持久化目录,而是从 YooAsset 的缓存文件区读取。所以 HybridCLR 的LoadAssembly要从抽象层拿字节数组,和资源插件的下载器解耦。

6.2 用 YooAsset 加载热更 assembly 的最短实现

假设你已经用 YooAsset 的YooAssets.LoadRawFileSync拿到了热更 dll 的字节:

using System.Reflection; using UnityEngine; using YooAsset; public class HotUpdateLoader { public Assembly LoadHotUpdateDll(string packageName, string dllName) { // 从 YooAsset 包中加载原始文件 RawFileHandle handle = YooAssets.LoadRawFileSync(packageName, dllName); if (handle.IsDone && handle.RawFilePath != null) { byte[] dllBytes = File.ReadAllBytes(handle.RawFilePath); byte[] pdbBytes = File.ReadAllBytes(handle.RawFilePath.Replace(".dll", ".pdb")); return Assembly.Load(dllBytes, pdbBytes); } Debug.LogError($"Failed to load raw file: {dllName}"); return null; } }

参数说明:packageName是 YooAsset 的资源包名,dllName是热更 dll 在远端目录的相对路径。这里用LoadRawFileSync而不是异步接口,因为程序集加载本身是同步操作,混入异步会增加启动时序复杂度。如果你在乎加载耗时,可以在下载阶段异步,但加载阶段必须同步。

注意这里的坑:YooAsset 4.x 和 3.x 的 API 名有差异,具体以你锁定的版本为准。凡是和资源插件耦合的代码,都要抽象成IAssetLoader接口,避免 HybridCLR 加载器直接依赖 YooAsset。否则未来换资源热更插件,热更这块代码也得重写。

6.3 混淆、加密与 WebGL 的边界处理

热更 DLL 和资源插件都会涉及加密或混淆。YooAsset 本身支持 AssetBundle 加密,对 HotUpdate DLL 来说,常见的做法是先把 dll 加密,再用 YooAsset 加载后解密成字节。HybridCLR 只负责加载字节,不关心字节来源,所以流程是:加密 dll -> 下载 -> 解密 ->Assembly.Load

混淆就要小心了。如果给热更程序集上了代码混淆,比如混淆类名、方法名,反射调用会全部失效。如果确实需要混淆,只混淆局部变量和字符串,不要混淆公共 API 和类型名,并且要在混淆后做一次完整回归测试。我见过最隐晦的问题是混淆器把MarshalByRefObject相关的元数据改掉,导致多线程委托调用崩溃而编辑器环境正常。

WebGL 是一个特殊平台。Unity WebGL 使用 idbfs 在浏览器里模拟文件系统,如果你把热更 dll 直接写到Application.persistentDataPath,在某些浏览器环境下会因为文件系统未初始化而写入失败,于是加载时File.ReadAllBytes会抛DirectoryNotFoundException。这种情况下要从 YooAsset 的缓存或内存中读取 dll 字节,不要依赖本地文件系统。另外 WebGL 的 IL2CPP 对多线程支持有限,浏览器没有真正的线程,HybridCLR 解释器在 WebGL 上仍是单线程执行,这不影响正确性,但会失去多线程带来的性能提升。

最后一个小技巧:调试线上问题时,在热更入口处打上“程序集版本号 + 文件大小 + MD5”的日志,再配合 YooAsset 的更新日志,就能快速定位是代码没更新还是资源包覆盖失败。这里的校验逻辑建议放在主工程 AOT 侧,不要放在热更代码里,因为热更代码本身可能因为版本不对还没跑起来。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询