Unity热更新介绍和测试方法

       最近项目中增加了热更新了功能,程序也完成了相应的开发,接下来就需要对这个模块进行相应的测试工作了,在测试开始之前,了解下其原理总是很有好处的。

1:什么是热更新

      简单的理解就是:用户重启客户端就能实现客户端更新的需求或者功能;或者也可以这样说:不需要通过在应用商店更新应用,就可以实现新的需求和功能。

2:为什么要热更新

      能够缩短用户取得新版客户端的流程,改善用户体验。

      就拿Ios来说,如果你需要更新应用,需要经过一系列的苹果提审流程,少则几天,多则一个月,审核周期比较难控制。试想下,如果你的上线游戏出现了一个严重的bug,此时如果没有热更新,那么只有两种选择:

第一种:提交更新包到应用商店,等审查(时间不受自己控制)

第二种:坐视不管

      无论哪种方式,你都应该清楚,可以提前准备简历和打包行李了。

      还有一个原因,对于大型游戏来说,玩家更新成本太大。App Store上,你更新一个应用,其实是重新下载一次,而不是增量的更新。

      如果有了热更新机制会怎么样呢?你可以通过热更新推送给每一个用户,在较短的时间(自己控制)内解决这个bug。领导对你十分肯定,走上人生巅峰指日可待了。

 

3:热更新分类

      热更新一般可以分为美术资源热更新和代码热更新。

      美术资源热更新的方案一般就是采用AssetBundle方式。

      代码热更新方案一般可以分为以下两种方法:

第一种方案:

      1:项目开发中,可以将部分逻辑提取至一个单独的代码库工程中,打包为DLL

      2:将DLL打包为AssetBundle

      3Unity程序动态加载AssetBundle中的DLL文件,使用Reflection机制来调用代码

       这个方案看起来很完美,能支持C#级别的代码热更新,但是它有限制:苹果官方禁止Ios下的程序热更新;JITIos下无效。所以这个方案在Android下还是可以用用的。但是一般情况下,一个项目不可能为AndroidIos开发两套不同的热更新方案,所以这个方案实际上也就无法应用到项目之中了。

-- ---------

       ps:稍微解释下AssetBundle这个概念,因为这个不是本文的主要目的,今后如果大家有兴趣的话,我再对AssetBundle进行详细的解释。

       AssetBundleUnity支持的一种文件储存格式,也是Unity官方推荐的资源存储与更新方式,它可以对资源(Asset)进行压缩,分组打包,动态加载,以及实现热更新,但是AssetBundle无法对Unity脚本进行热更新,因为其需要在打包时进行编译。

-----------

第二种方案:

       lua插件。在Unity环境里内嵌一个lua虚拟机,经常变动的和对执行效率没要求的逻辑用Lua实现,游戏启动时加载服务器上最新的lua字节码来执行游戏。lua代码都是运行时才编译的,不运行的时候就如同一张图片、一段音频一样,都是文件资源;所以更新逻辑只需要更新脚本,不需要再编译,因而lua能轻松实现“热更新”。

       按照这个逻辑来说,其实诸如pythonjavascript等脚本语言的话,都是可以实现这个功能的。只不是目前几个开源的、成熟的热更新方案都是基于lua的。

-----------

       ps:这里你可能需要对脚本语言和编译性语言的区别进行一个大致的了解。这个网上解释很多,自行google就好。

-----------

 

4:热更新原理

      热更新主要的原理有2个:

      1lua是一种脚本语言,不需要事先进行编译,而是直接读取文本文件,一边解释一边执行

      2Application.persistentDataPath目录是一个可读写目录,可以将需要热更新的资源和代码下载到这个目录中,当游戏中使用这个对象的时候,首先检查是否在这个目录中存在,存在的话,则运用这个新的资源,不存在的话就是使用原先的。

 

5:热更新基本流程

       简要说下目前我们项目的一个热更新基本流程:检测补丁—>下载补丁—>应用补丁。

       检测补丁:包括是否需要更新,需要更新哪些文件

       下载补丁:根据补丁检测的结果,到patch服务器下载对应的更新zip

       应用补丁:解压zip包,将补丁运用到游戏中

-----------

ps:这里要特别说明下,lua文件的存放位置:

       一般情况下我们将lua文件存在放StreamingAssets文件夹中。在不同的平台上面 (Windows, Ios ,Android)StreamingAssets目录最终发布的位置不同,所以读取的方法也不同。

        Ios中,我们可以通过文件流的方式读取lua脚本,但在Android下,不支持C# IO流方式读取StreamingAssets下的文件,因为Android手机中StreamingAssets下的文件都包含在压缩的.jar文件中(这基本上与标准的zip压缩文件的格式相同),这意味着,如果你不使用Unity中的www类去检索文件,那么你需要使用额外的软件去查看.jar的存档并获取该文件。但是unity项目中,我们需要尽量减少www的使用,www的使用会增大mono内存池。那么怎么办呢?所以在Android下,我们进行了特殊处理,将所有的lua脚本都提前复制到Application.persistentDataPath目录下,然后再通过IO流去访问。这里再啰嗦一句,刚提到的StreamingAssets目录是一个只读的文件,Application.persistentDataPath目录是一个可读写的目录,热更新的实现原理也主要是用到了Application.persistentDataPath目录可读写的特性。

        此外你可能还需要了解unity项目中,一些基本目录的含义。

-----------

6:热更新测试

       这次测试我采取了白盒测试和黑盒测试相结合的测试方案。

       白盒测试:通过review热更新的相关代码,在各个判断条件处加入debug log,同时根据if的判断条件,构建不同的测试环境,保证每一个路径都覆盖到。例如:各种网络状态不对的提示是否正确;patch服务器各种状态的提示是否正确;patch文件各种状态的提示是否正确;

       黑盒测试:模拟玩家正常的操作流程,同时结合中途打断,断网等异常的操作。

       还有一个测试的时候的小技巧:当通过补丁构造脚本完成补丁构造后,怎么通过实际的运行游戏查看补丁是否被正确应用呢?这里我采取了一个猥琐的方法,就是通过手动解开patch包,然后在其中塞入自己手动的lua脚本和bundle,再手动压缩成patch包,供下载更新。其中自己手动加入的luabundle就可以很方便的实现加入一些调试用的log信息等,这样在运行游戏的时候,就能方便的识别出热更新是否成功了。

       这里如果说的不明白,可以直接联系我,再当面交流。

 

7:提外话

       值得注意的是,即使是使用lua,仍然是不符合appstore的政策,依然存在被下架的风险。

       目前发生的JSPacth事件,就是很好的说明。38号左右,不少开发者都收到了苹果的警告邮件,在邮件中,苹果称开发者使用了动态代码更新技术,要求开发者删除相关代码,并重新提交一个新的 App 版本以供审核。虽然最后是虚惊一场,只是针对使用了JSPatch的相关应用。那为什么同样是热更新,JSPatch就收到了苹果的警告呢? JSPatch 可以理解为所有的 Objective-C API 进行了映射,允许开发者在 JS 端调用任意原生代码,这显然是极其危险的。假设这段代码是通过热更新技术下载执行的,如果在中间存在黑客,把这段代码动态替换掉,比如修改为获取用户通讯录并上传到黑客的服务器,就会造成重大的安全问题。

        那诸如我们使用的lua热更新为什么就可以被理解为是安全的呢?与 JSPatch 不同的是,lua热更新技术主要的实现方式是把动态脚本下载之后,让动态脚本调用游戏引擎提供的接口实现缺陷修复。与 JSPatch 不同的是,动态脚本并不能任意调用全部原生代码,而是只能根据游戏引擎提供的接口调用相关功能。在这个过程中,游戏引擎的原生端作为一个安全沙箱,提供了一个安全的保护层,只要游戏引擎不要对外提供获取通讯录的接口,黑客就无法通过替换动态脚本的方式获取用户的隐私资料。进而可以被认为是安全的,自然不在苹果的禁止范围内。

 

本文来自网易实践者社区,经作者朱洁授权发布。