一小时急速掌握ILRunTime热更新


版权声明
本文为“优梦创客”原创文章,您可以自由转载,但必须加入完整的版权声明
更多学习资源请加QQ:1517069595或WX:alice17173获取(企业级性能优化/热更新/Shader特效/服务器/商业项目实战/每周直播/一对一指导)
点赞、关注、分享可免费获得配套学习资源
认识ILRuntim

什么是热更新:  
热更新是指软件不通过运营商店的软件版本更新审核,直接通过应用自行下载的软件数据更新的行为
简单来说就是在用户下载安装APP之后,打开App时遇到的即时更新,热更新是一种各大手游等众多App常用的更新方式,目前市面上的常见热更新方案有ILRuntime、ToLua、Xlua等
为什么需要ILRuntime:
ILRuntime是基于C#平台的,所以ILRuntime的使用只需要会C#语言即可
ILRuntime的热更新是非常快捷且可靠的,因为它的底层使用的是.NET的运行环境,所以只要.NET运行时的环境稳定,ILRuntime就是稳定的
ILRuntime也可以在不支持JIT的硬件环境下进行代码的热更新。例如IOS环境出于安全考虑不允许在程序运行期间动态加载一块程序代码到内存中运行,所以使用ILRuntime或XLua可以实现代码的热更新
注:JIT为即时代码编译运行功能

ILRuntime的优势:
如果使用Lua的热更,需要在Lua脚本和C#脚本之间加一层脚本的绑定,而ILRuntime不需要
ILRuntime可以直接使用VS2015以上版本进行开发,ILRuntime的解译引擎支持.NET4.6编译的DLL
ILRuntime的执行效率高,是L#的10-20倍。如果不是使用反射,而是使用基于CLR绑定的方式可以使跨域调用的性能达到sLua的2倍左右
ILRuntime可以支持跨域继承。即ILRuntime的脚本可以继承其他C#的类
ILRuntime支持泛型,这点Lua是不可以的
ILRuntime拥有VisualStudio的调试插件,因此它的调试非常方便。

C#与Lua的对比:
C#和Lua作为热更新方案各有优缺点
Lua对于Unity项目而言是有非常明显的缺点的。使用Lua的话需要开发人员掌握Lua和C#,或者将团队中的人员分成C#和Lua小组,适合有足够资源的大型团队。对于中小型团队会很痛苦
使用C#作为热更语言的话,只要求开发者会C#语言即可,对于Unity项目而言,这种方式是非常高效的,也适合中小型的开发团队
Lua的优势在于解决方案足够成熟,也有很多开发者比起C#作为热更语言,更加熟悉Lua开发。而且借助luajit,在某些情况下的执行效率会非常不错,不过luajit现在的维护情况也不容乐观,对开发者的要求比较高,所以官方还是推荐使用公版Lua进行开发
建立第一个ILRuntime程序

首先创建一个新项目。(最好使用unity2019及以上版本)

点击菜单栏的Window-PackageManager打开插件包管理器。

点击Advanced勾选Show preview packages。

搜索ILRuntime并安装。

安装完成之后在上图位置点击Import into Project导入官方示例工程(Demo)

导入成功之后会发现Console窗口报错了,这是因为导入的Demo中写了不安全的代码导致的

点击Edit-ProjectSettings打开项目设定

点击Player,在右边OtherSetting下找到Allow unsafe Code这一项,打上勾,报错就消失了。这是让Unity允许使用不安全的代码

报错解决之后,按照上图目录打开导入的HellowWorld场景并运行

又会报错,因为:

场景内GameObject上有个脚本HelloWorld,点击打开

HelloWorld脚本一开始会执行LoadHotFixAssembly方法(具体的代码分析在下一节)

上图是LoadHotFixAssembly方法的流程,而报错的原因就是第一步加载StreamingAssets/HotFix_Project.dll时没有找到该文件。解决办法如下:

鼠标右键导入的Demo中的1.6.4文件夹点击Show in Explorer在资源管理器中打开它

依次双击1.6.4-Demo,进入Demo目录

可以看到有两个文件夹在Unity的Project面板里没有显示,这是因为Unity规定文件夹后加符号‘~’,就不会在Unity中显示。双击HotFix_Project~文件夹

双击HotFix_Project.sln文件,打开这个热更的项目工程。

右键HotFix_Project点击生成,编译一个动态链接库


成功生成,Unity里已经有了HotFix_Project.dll文件,回到Unity项目中再次点击运行

这时Unity的Console面板没有报错,并看到Unity控制台窗口出去文字”!!!InstanceClass.StaticFunTest()”,说明成功运行了项目
ILRuntime源码分析

这是HelloWorld.cs的流程图。我们来分析一下刚刚的HelloWorld脚本:

首先,这个脚本开启了一段协程运行LoadHotFixAssembly方法

这个脚本首先创建了一个AppDomain,Appdomain是一个应用程序域,每个AppDomain都是一个独立的沙盒

这里,LoadHotFixAssembly加载streamingAssets文件夹下的HotFix_Project.dll文件,并读取到内存中。
appdomain.LoadAssembly(HotFix_Project.dll),将热更dll读取到热更脚本引擎

然后调用InitializeILRuntime()方法:初始化ILRuntime脚本引擎。最后OnHotFixLoaded()方法:执行脚本引擎加载后的逻辑处理。这就是LoadHotFixAssembly方法干的事。我们再来看一下OnHotFixLoaded()方法

这段代码的意思就是:调用HotFix_Project的项目里的InstanceClass命名空间下的StaticFunTest方法。我们去看一下这个方法:


里面只有一个语句意思是:输出”!!!InstanceClass.StaticFunTest()”。这就是我们刚刚在运行Unity时在Consle面板输出的文字
深入ILRuntime热更新原理

通过上面的代码解析我们已经了解到,Unity主工程可以通过appdomain.LoadAssemblyPreobject.dll”)去加载热更新dll。我们可以把Unity主工程和HotFix_Project.dll叫做两个程序域(AppDomain)。程序域和程序域之间是不能互通的
举个例子:
网通和电信的网络,走的是两条不同的线路,它们的硬件在物理上是不互通的,如果想要网通的用户和电信的用户在一局游戏里能够互相对战,就需要一个双线服务器,这个双线服务器就是一个中介桥梁,它把网通的数据发给电信,电信的数据发给网通,这样两边的用户就可以在一局游戏里战斗了
所以Unity的主工程的数据类型和热更工程的数据类型是不直接互通的。Unity的主工程的数据类型和热更工程的数据类型要想实现互通的话,就需要实现一些跨域功能

跨域委托:
需要额外添加适配器或者转换器
例如:在热更工程中通过一个代理去调用主工程的方法,需要实现跨域委托(想知道关于跨域委托的实现可以添加老师联系方式)
跨域继承:
如果想在热更工程里继承主工程的类/接口,则需要在Unity主工程中实现一个继承适配器并注册。这样热更工程就可以调用继承适配器进行继承
反射转换:
热更工程中的数据类型和C#数据类型是不通用的,所以需要类型映射后使用
CLR重定向:
ILRuntime之所以能达到高性能,是因为它会劫持热更工程中可能出现GC的方法调用,它可以进行重定向。我们就有机会将可能产生GC的功能替换成我们自己的实现,从而实现高效能(更多请参见官方文档)
CLR绑定:请参见官方文档(也可添加老师联系方式进行了解)

ILRuntime借助Mono.Cecil库读取DLL的PE信息(程序的一些基础信息),解析其中所有类型,得到IL汇编码,通过IL解释器执行
为了得到高性能运算,ILRuntime自己实现了一个脚本栈,通过脚本栈,提高运行效率。如果没有脚本栈,C#会将所有的数据类型放在Stack类中,所有数据类型都会作为Object类型存储,产生大量的拆箱和装箱,因此性能会非常低下(添加老师联系方式,获取VIP课程,在VIP课程中会对此处有详解)
ILRuntime使用unsafe代码(指针)以及不会产生GC的内存管理方式(手动分配手动释放),以此来达到高性能(VIP课程也会对此处代码进行详细分析)

如果要对比ILRuntime和Lua的性能,最好亲自去进行测试,测试要在Release模式下进行测试
使用ILRuntime后,在进行项目打包时,要关闭Development Build选项,不然会增加额外负载,导致性能测试不准确
避免GC:
1,最好不要使用ILRuntime跨域调用默认采用反射,或者尽量少用,多用CLR绑定或基于InvocationContext的调用(详细请添加老师的联系方式) 
2,一些值类型的数据,比如Vector2、Vector3,如果直接使用C#的值类型的数据,中间会存在数据类型转换,从而产生GC,所以应该基于托管栈重新实现值类型的代码绑定(详细请添加老师的联系方式) 
3,一些频繁调用的方法(比如Update方法)要避免可变参数列表,因为会new数组出来,如果每一帧都去new一个数组,就会产生大量GC

商业项目中的ILRuntime解决方案:
如果脚本代码是基于MonoBehaviour的话,脚本是不能进行热更的,所以需要一个不依赖于MonoBehaviour的代码框架(在老师的皇室战争课程中有教学)
为了避免GC,要多用CLR绑定,但是如果所有CLR绑定都手写,这个工作量极大,所以要进行自动化CLR绑定生成
与Addressable资源管理和热更系统相结合将会更好提升开发效率
写在最后
更多学习资源请加QQ:1517069595或WX:alice17173获取(企业级性能优化/热更新/Shader特效/服务器/商业项目实战/每周直播/一对一指导)
点赞、关注、分享可免费获得配套学习资源
到顶部