热更新知识点汇总
热更新的概念
游戏或者软件更新时,无需重新下载客户端进行安装,而是在应用程序启动的情况下,
在内部进行的资源或者代码更新。
热更新的优点
- 迅速修复Bug – 避免重新下载安装包,游戏内部及时更新Bug
- 减小安装包的体积 – 非核心资源上传服务器,运行时动态加载剩余资源
- 迅速更换游戏"内核" – 狸猫换太子,绕过审核,迅速对游戏进行更新
热更新解决方案
-
基于Lua的热更新解决方案
-
原理:本质上就是利用相关插件(
xlua,tolua
)提供一个Lua的运行环境(虚拟机),为Unity提供Lua编程的能力,让C#和Lua可以相互调用和访问。 -
优点:
- Lua由C语言编写通用性强几乎在任何操作系统均可运行。
- Lua作为轻量小巧的脚本语言,由Lua虚拟机解释执行,热更只需进行简单的文件替换,无需进行编译。
- 开发经验浓厚,有很多成熟的项目和方案。
-
缺点:
- Unity原生使用C#,多使用一门Lua语言会增加学习和开发成本
- Lua是弱类型非面向对象的语言,面对较大的项目时将无法像传统OOP语言一样分层和模块化开发等,容易造成代码结构混乱和维护困难等问题。
- 需要利用插件在C#环境下提供Lua运行环境,并且还需要为Lua和C#之间提供数据的通信和相关转换,效率低下,远不如用原生C#进行开发。
-
解决方案
-
XLua
-
基本原理:
XLua
框架的基础是实现了运行在C#环境上的Lua虚拟机使得Lua可以和C#实现相互调用和访问。 -
C#调用Lua原理:Lua由C语言实现自带C/C++的通信机制接口,C#可以借助C与Lua进行数据通信,在内存上进行数据交换。从内存中直接获取到的数据引用还需进一步转换成C#的相关类型才能使用,
xLua
在castersMap
中定义了lua
数据类型到C#类型的转换函数,转换后即可实现C#调用Lua。 -
Lua调用C#原理:数据通信原理同上,简单基本值类型通过C API可轻松实现传递,但复杂的对象则需要通过Lua的
userdata
表和C#对象的映射实现,每个C#对象均在Lua中对应一个userdata
表,然后lua
会为userdata
设置元表,元表中表示的是实际对象的类型信息,在C#对象传递到Lua后还需告知诸如对象的类型,静态/成员方法、属性等信息注册到Lua后,Lua才能正确的调用C#相关对象函数等。元表在此可以理解为是C#中的Type类,所有C#对象均有一个类型对象指针,类型对象中存有方法表,静态字段等信息,
userdata
表只存有类中具体的成员,还需要元表充当类型对象才能正确的和C#对象映射成功。 -
热补丁原理:利用IL注入,实现用Lua函数替换C#原函数。实现的基本原理为:根据Lua脚本热补丁命令生成匹配文件(
DelegatesGensBridge.cs
)里面存有Lua提供的热补丁函数引用,再根据特性的标注[Hotfix]等,定位到目标函数上创建静态变量DelegateBridge
。当Lua为其提供了热补丁函数,则此变量不为空否则为空,再利用此变量进行条件判断,实现不为空时根据匹配文件中Lua函数引用去执行,不继续向下执行原C#函数。若为空则执行原C#函数逻辑。
-
-
ToLua
- 基本原理:
ToLua
框架主要是通过静态绑定来实现C#与Lua之间的交互的。 - C#调用Lua原理:数据通信和
xLua
类似,获取内存中数据引用后也需要进行转换,转换方式和XLua
有所区别,ToLua
实现了LuaInterface
建立了Lua与C#之间的映射,使用C#的类型建立了lua
的基础数据模型,然后在这个基础上建立了运算规则。 - Lua调用C#原理:与其它框架不同的正是其静态绑定的特点,在Lua调用C#时,其提供了一个C#文件(
CustomSettings.cs
)显示注册C#组件,启动虚拟机时会利用LuaInterface
将注册了的C#组件映射到Lua上,包括基础类型的转换、自定义类型到table的转换、函数的转换,最终均注册到Lua的大G表(global table)中实现Lua调用C#。
- 基本原理:
-
两种Lua解决方案的比较
ToLua
提供的暖更新不完善,C#项目转Lua做热更新建议用XLua
的热补丁。ToLua
采用的是静态绑定在文件中显示注册的方式减少了反射次数,XLua
则是提供了一系列特性也一定程度上减少可反射次数。ToLua
对泛型的支持糟糕,会有大量的拆装箱过程,效率较低。XLua
完善了对泛型的支持,减少了拆装箱的次数。
-
-
-
基于C#的热更新解决方案
- 原理:本质上用编译好的新DLL替换需要更新的旧DLL。只能进行代码热更新,还需配合AB包的资源热更新一起使用。
- 优点:
- 和Unity开发均使用C#,开发语言统一,编码更容易。
- 使用纯C#开发无需另创虚拟机等环境,效率高,性能远高于Lua
- 缺点:
- 方案均有各自的局限性,直接反射方式不仅损耗性能,在不支持JIT的系统(IOS)上无法使用。
ILRuntime
解决了JIT的问题但不够成熟,使用问题较多,需要较高的动手解决问题能力。
- 解决方案
- DLL反射热更新: 使用编译后的DLL文件进行替换,利用反射方式把所需C#组件绑定到相应的对象上使用。
ILRuntime
:本质还是DLL的替换,但实现了一个ILR(IL运行时)使其能够在不支持JIT的硬件环境(IOS)下实现代码热更新。
热更新基本流程
- 版本号的比较,如果版本号不同才继续以下流程(version.txt记录版本号,可选)
- 下载资源服务器上的对比文件(files.txt记录所有文件md5码,类似于目录)
- 确定下载列表,将最新下载的对比文件和本地旧对比文件对比,记录缺少或不同的文件。(利用files.txt中的md5码实现此步骤)
- 根据下载列表,下载所需的资源。(一般放在
Application.persistentDataPath
) - 解压(如果文件压缩过需要先解压,可选)
- 保证下载成功后,用最新的对比文件覆盖本地的对比文件(更新目录)
热更新规则
-
资源打包方式:需要更新的代码和资源,都必须打包成
AssetBundle
(AB包)进行下载和加载,AB包的特性适合做热更新。(理由如下)- AB包存储位置自定义,继而可放入可读可写的路径下便于实现热更新
- AB包自定义压缩方式,可以选择不压缩或选择LZMA和LZ4等压缩方式,减小包的大小,更快的进行网络传输。
- 资源可分布在不同的AB包中,最大程度减少运行时的内存压力, 可做到即用即加载,有选择的加载需要的内容。
- AB包支持后期进行动态更新,显著减小初始安装包的大小,非核心资源以AB包形式上传服务器,后期运行时动态加载,提高用户体验。
-
资源存放路径:优先选择将需要进行更新的代码和资源放在
Application.persistentDataPath
,相较其它特殊目录,该目录下可读可写,运行时有效且无内容限制更适合做热更新开发。(几个重要路径的对比如下)-
Resources
-
Resources文件夹下的资源无论使用与否都会被打包
-
所有资源全部打成一个大包,查找加载效率低下。
-
资源会被压缩,转化成二进制
-
打包后文件夹下的资源只读
-
无法动态更改,无法做热更新
-
使用
Resources.Load
加载
-
-
StreamingAssets
- 流数据的缓存目录
- 文件夹下的资源无论使用与否都会被打包
- 资源不会被压缩和加密
- 打包后文件夹下的资源只读,主要存放二进制文件
- 打包后无法修改,无法做热更新
- WWW类加载(一般用
CreateFromFile
,若资源是AssetBundle
,依据其打包方式看是否是压缩的来决定) - 相对路径,具体路径依赖于实际平台
Application.streamingAssetsPath
路径在不同平台下有时不准确
-
Application.dataPath
- 游戏的数据文件夹的路径(例如在Unity Editor中的Assets)
- 无法做热更新
-
Application.persistentDataPath
-
持久化数据存储目录的路径( 沙盒目录,打包之前不存在 )
-
文件夹下的资源无论使用与否都会被打包
-
运行时有效,可读写
-
无内容限制,从
StreamingAsset
中读取二进制文件或从AssetBundle
读取文件来写入PersistentDataPath
中 -
适合热更新
-
-