找回密码
 立即注册
查看: 672|回复: 0

[笔记] Unity移动端代码热更新技术学习总结

[复制链接]
发表于 2020-12-15 09:15 | 显示全部楼层 |阅读模式
为什么需要热更新

    游戏总是伴随着不断的开发与维护,我们不能要求玩家每次都将游戏客户端卸载重装,所以需要热更新技术来在不需要重装客户端的情况下下载更新游戏里的代码(其实资源也需要热更新,但是因为操作系统没有对资源文件热更新做限制,所以这里按下不提)。
直接覆盖DLL

    这里只说android平台,unity打包安装包之后会生成一个apk文件,该文件事实上是一个zip文件,只是后缀名不同,解压可以看到在 assets\bin\Data\Managed\ 目录下有 predefined assemblies,也就是脚本编译成的DLL(默认名为Assembly-CSharp,在VS的 Solution Explorer 里可以看到),这就是游戏运行时代码所在,如果可以将新代码的dll直接覆盖替换,即可实现热更的目的,自定义 assemblies 的情况也差不多,覆盖需要的dll就完事了(要注意版本,宏之类的坑)。上面说的是mono的情况,IL2CPP的话,则是另外的目录(\lib\armeabi-v7a)这里有个小坑,so文件中存放的只是代码段,数据段(比如字符串)是在\assets\bin\Data\Managed\Metadata\global-metadata.dat 中。我测试的时候只改了覆盖了so文件,结果修改没有生效让我困惑了老半天 。unity 用于快速迭代的pacth功能,也是类似的原理。不过因为ios平台上不支持JIT而且还禁止dll热更,所以不支持这种方法,所以如果只开发安卓平台的话,就可以用这种方法,简单直观。不过现实是稍微正经的项目都是要支持ios的,毕竟市场占有率摆在那。。所以接下来回到现实。
基于Lua的热更

单纯使用Lua开发

    Lua 是一种脚本语言,可以运行再对应平台的虚拟机上。Lua 在虚拟机上是被 解释-执行,而不是像 JIT 一样先编译成对应机器码,所以不会受到IOS的限制。我们所需要做的就是在程序里嵌入一个Lua解释器然后在运行时读取脚本并解释执行(当然,还要再C#与Lua代码之间做一些数据通信,以及在Lua代码支持对C#的调用 )。比如像下面这样的代码
  1.     void TryLoadScript()
  2.     {
  3.         string path = Path.Combine(Application.persistentDataPath, "test.lua");
  4.         using (TextReader textReader = new StreamReader(path))
  5.         {
  6.             script = textReader.ReadToEnd();
  7.         }
  8.     }
  9.     private void OnClick()
  10.     {
  11.         LuaEnv luaenv = new LuaEnv();
  12.         luaenv.DoString(script);
  13.         string s = luaenv.Global.Get<string>("s");
  14.         text.text = s;
  15.     }
复制代码
通过覆盖脚本即可实现逻辑的更新。lua方案相比起C#通过 JIT 或者 AOT 得到的 native code 一般而言性能上相对弱势,更不用说还有 IL2CPP这种东西。把性能敏感模块弄成非 lua 代码还是相当地有必要的。所以还是需要提前确定好哪部分代码是需要热更的,需要项目把频繁迭代的业务模块以及稳定的基本功能模块划分开来,(不过大部分项目早期开发基本所有地方都是要频繁地迭代修复的。。。)。
C#转Lua

    Lua 这种弱类型语言开发效率与维护体验对很多人来说都是不如C#的,于是就出现了一些能够将C#代码转换为Lua的工具。比如这个。但是显然lua不可能把所有特性都支持了,实际项目也不是所有代码都需要热更新,实际搞进工作流还是有很多的麻烦的,不过幸好已经有了很多比较成熟的方案,比如 xlua 。总的来说这类方案的核心热更原理还是基于Lua解释型脚本语言的特点。
ILRuntime

    Unity 是通过Mono来将  C# 编译为 CIL,然后通过将 CIL 运行不同平台的CLR上来实现跨平台。前面提到了IOS禁止动态加载dll与JIT。但是并没有禁止脚本解释。ILRuntime即是实现了一个IL的运行时,或者说解释器,把dll中的 IL 指令像脚本一样解释执行,而不是通过JIT或者AOT编译为机器码。这实在是一个思路清奇的东西,整出了C#编译成IL运行一个能解释IL的解释器并用他来解释执行别的IL这样给人套娃感觉的东西,作者是真的强。
    因为IOS还是禁止dll更新,所以要把编译完包含 IL 的 dll(还有pdb,如果希望有调试信息的话) 打包成另外的文件加载,然后在运行时动态加载、解析、解释、执行。
    ILRuntime的好处是,开发语言可以继续使用C#(或者别的能编译成IL的语言)进行开发。同时还能实现热更。开发效率比纯 Lua 的高,支持的C#特性比C#转Lua的方案要多。缺点是性能较差(仅限不支持JIT的平台,有JIT的话ILRuntime会跑得更快,性能能被拉平),主要是体现在一些计算密集的地方,相比起Lua会有劣势。
    然后使用时还需要注意跨域继承(注意不要继承 Mono Behavior,坑太多。。),如果脚本代码也需要热更的话,就对几个事件函数做一下重定向(当然,这里得用ILRuntime调用热更代码的接口,我见过居然用反射来重定向到热更代码的人。。)、传递闭包时需要写适配,还有用 CLR绑定和值绑定优化性能热点,不过官方提供了一些自动生成代码的工具,结合项目情况改改还是问题不大的,至少不会比写Lua恶心。 如果使用IL2Cpp的话,还要注意对泛型方法做CLR绑定以及类型裁剪的坑。更多细节可以看官方文档。
代码文件的热更

    上文中没有提及到的一个很重要的点就是没有提及代码文件(Lua脚本,或者DLL)如何下载覆盖更新,事实上这个问题与代码热更新关系不大,可以把这些代码文件当成普通的资源从服务器上下载、更新、加载,当然这个过程还涉及到一些版本比较之类的东西,本文就不详细展开了。
参考链接
懒得打字嘛,点击右侧快捷回复 【右侧内容,后台自定义】
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Unity开发者联盟 ( 粤ICP备20003399号 )

GMT+8, 2024-11-22 23:32 , Processed in 0.088258 second(s), 25 queries .

Powered by Discuz! X3.5 Licensed

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表