跳到主要内容

热重载技术

热重载技术用于完全卸载或者重新加载一个assembly,适用于小游戏合集类型的游戏。该方案只提供商业化版本

支持的特性

  • 支持卸载assembly,卸载100%的assembly所占用的内存
  • 支持重新加载assembly,代码可以任意变化甚至完全不同(MonoBehaviour和Scriptable有一定的限制)
  • 支持限定热更新assembly中能访问的函数的集合,适合UGC游戏中创建沙盒环境,避免恶意玩家代码造成破坏。

不支持特性及特殊要求

  • 要求业务代码不会再使用被卸载的Assembly中的对象或者函数,并且退出所有在执行的旧逻辑
  • 不能直接卸载被依赖的Assembly,必须按照逆依赖顺序先卸载依赖者,再卸载被依赖者。例如A.dll依赖B.dll,则需要先卸载A.dll,再卸载B.dll
  • MonoBehaviour、ScriptableObject、以及标记了[Serializable]的类型
    • 要求重载的MonoBehaviour中的事件或消息函数如Awake、OnEable之类不发生增删(但函数体可以变化)
    • 要求重载后在旧Assembly中存在同名脚本类的序列化字段名不发生变化(类型可以变)
    • 如果字段类型为可卸载程序集中的自定义类型A(class或struct或enum),必须给它加上[Serializable]特性
    • 不支持字段类型为List<A>其中A为可卸载的程序集中的类型,请替换为A[]
    • 不能是泛型,也不能继承泛型类型,例如class MyScript<T>class MyScript : CommonScript<int>
  • 一些会缓存反射信息的库(这种行为在序列化相关的库中最为普遍,如LitJson),在热重载后需要清理掉缓存的反射信息
  • 不支持析构函数,~XXX()。也不允许实例化泛型参数带本程序集类型的带析构函数的泛型类
  • 与dots不兼容。由于dots大量缓存的类型信息,实现复杂,很难单独清理掉缓存信息。

内存卸载率

除了以下元数据内存无法卸载外,其余几乎所有(99.9%)元数据都可以被卸载:

  • MonoBehavoiur、ScriptableObject之类的脚本类。它们在运行时层面对应的Il2CppClass会被Unity引擎内部引用,无法释放,但可以释放掉绝大多数成员元数据如method
  • 被标记[Serializable]的类型。与MonoBehaviour类似,它们也可能在序列化过程中被Unity引擎内存引用,无法释放。
  • 在本程序集运行过程中使用到,但不涉及到本程序集类型的泛型类。如List<int>元数据不会被释放,但List<MyHotReloadClass>会被释放

所有未释放的元数据(MonoBehaviour、Serializable类)在再次加载该程序集时会被复用。多次加载和卸载同一个程序集,只会发生一次未释放行为,不会导致泄露或者未释放的元数据内存持续增长。

实际项目中,对于大多数程序集可以卸载掉99%以上的元数据内存。