这是Unity Android il2cpp的完美热更解决方案的Demo(Git地址)的说明。android
和现有的热更解决方案不一样的是,他不会引入多余的语言(只是UnityScript,c#...),对Unity程序设计和编码没有任何限制。你能够在预置和场景里的GameObject上添加任何的Compnents组件,须要序列化的和不须要序列化的,他们都是能够热更的,也不须要作额外的标记处理。简而言之,在此方案下,Unity的全部资源和脚本,都是能够热更的。git
本文接下来将介绍如何去制做热更文件和如何应用这些热更文件。为了简化Demo的设计,Demo包含的热更文件会事先以全量更新的方式制做好,一块儿打到了Apk里面。具体到项目中热更文件得放服务器,正式上线得放CDN,以增量更新的方式捣鼓出和文中同样的目录结构就OK了。github
Unity在以il2cpp方式导出Android工程(或者Apk文件)的时候,代码会被编译成libil2cpp,而相关的资源、配置和序列化数据会以他们各自的格式导出到android的assets目录(assets/bin/Data)。这两部分,libil2cpp和assets目录,必须匹配(即须要在同一次打包中提取,可能有的变了,有的没变,增量方式只提取变化的部分)才能正常工做,否则Unity会在启动时崩溃。本方案就是热更这两部分。bootstrap
热更的正式流程以下图.c#
流程说明:windows
步骤1,在Unity的逻辑以前,libbootstrap会检查本地是否有Patch. Apk安装后,没应用过任何热更,本地是不会有Patch文件的,走no流程。若是热更过,则会有Patch目录,走yes流程。Patch目录如何准备,后面会将到。bash
步骤2,加载Patch目录对应架构(arm/x86)的libil2cpp库,并应用assets目录的更新文件。服务器
步骤3,开始Unity的流程,进入Unity第一个场景,并执行相关的Unity Script,通常是C#,咱们都以C#举例。架构
步骤4,检查服务端是否有新的patch,这步demo没有演示,须要本身实现。app
步骤5,下载新的patch,这步demo也没有演示,须要本身实现
步骤6,根据规则准备patch目录,详细规则会在后面描述。在Demo中只是将全量更新包解压,全量更新包打包的时候目录结构就是对的,因此不须要作其余的处理。
步骤7,调用libbootstrap的接口设置patch目录,由于libil2cpp已经加载进进程,因此须要重启APP,重新的patch目录加载patch。这步Demo中有设置patch目录的例子。
步骤8,重启APP,Demo提供了纯C#代码。
步骤9,没有新的Patch,就正常进入游戏了。
流程里更详细的描述和如何生成Patch文件,见第三章。
工程全部文件均置于AndroidIl2cppPatchDemo目录下。各文件目录说明以下表。
名字 | 说明 |
---|---|
Editor/AndroidBuilder.cs | 这个文件包含全部从导出Android工程,到输出Patch和生成Apk安装文件的代码。 |
Editor/Exe/zip.exe | zip压缩工具,用来将asset/bin/Data下的文件压缩成标准zip格式。 |
Plugin/ | 包含libbootstrap库. |
PrebuiltPatches/ | 包含预先生成的两个全量热更新版本。 |
Scene/*.unity | 演示场景,母包和版本1仅有0.unity,版本2增长了1.unity,测试新增场景和脚本的patch |
Script/Bootstrap.cs | 这个文件定义了libbootstrap的c#接口和重启APP的纯c#实现 |
Script/VersionSettor.cs | 这个脚本用于运行时准备相应的热更版本目录。 |
Script/UI/MessageBoxUI.cs | 这是一个简单的运行时MessageBox控制器。 |
ZipLibrary/ | c#版的压缩解压工具,输出的zip文件为非标准文件,Patch制做中不能用于asset/bin/Data文件的压缩,仅用于libil2cpp库的压缩,运行时用于全量热更包的解压. |
全部文件就这么多,项目用git管理,master分支为母包分支,version1和version2分支为热更1和热更2分支,分支间会有些细微的差异,version1主要测试序列化数据,version2添加了新场景和新脚本,具体能够diff查看。下面会详细描述打包过程和如何应用热更文件。
全部的打包逻辑在文件Editor\AndroidBuilder.cs里。展开主菜单AndroidBuilder, 能够看到有5步,为了和热更启动流程区分,咱们就叫他过程。
过程2:须要修改一下Android工程,由于libbootstrap须要在进入Unity的帧循环前,检查加载本地准备好的patch。大多数状况,你能够复用这个步骤的代码。可是若是你的项目修改了Unity Java的继承体系,你须要检查一下这块代码是否有调用到。若是没有调用到,后面Unity帧循环中的逻辑和资源,用的都是Apk内的相应文件。
过程3:生成热更文件。如在第二章所述,patch分为两部分,il2cpp库和assets/bin/Data目录。具体作法代码均有提供,须要注意的是必须遵照各个文件的命名方式和相对路径。各个文件均有压缩,对于增量包,若是压缩前的文件和以前相比没有变化,则不须要制做对应的压缩文件。这部分制做压缩部分的代码可复用,增量部分须要本身实现,热更文件最好也加进版本管理(svn/git/...)中。
过程4: 生成打包的windows脚本。脚本仅依赖JDK/SDK命令,可复用。生成脚本后,Android工程就不依赖Unity了,能够随意替换文件,再次调用脚本生成新的Apk。须要注意的是,打包用的so动态库,是pkg_raw目录下的so文件,替换时请注意。首次会在Unity目录下生成keystore目录和相应的签名文件,能够将此签名替换,并修改导出脚本中的签名密码。
过程5: 执行过程4中的脚本,生成Apk安装文件,可复用。
主菜单AndroidBuilder下还提供了菜单“Running Step 1, 2, 4, 5 for the base version”,这是一键构建母包版本用的,母包不须要制做patch文件,因此少了过程3;和菜单“Runnnig Step 1-4 for patch versions”,这是一键构建Patch用的,由于在demo里,不须要导出Apk文件。
关于打包这里得多说两句。 若是没有采用AssetBundle的方式打包,Unity会按各自格式,将全部场景和依赖输出到assets/bin/Data目录,这样子也是能够热更的。可是,不要这么作,由于这样作微小的改动会影响到多个文件,致使热更文件过大。最好是本身用AssetBundle的方式将资源作一个清晰的划分,打包好的AssetBundle放在assets下的其余目录。须要注意和libil2cpp库和assets/bin/Data的文件向匹配(保证是同一个版本的输出)。运行时能够重写AssetBundleManager.overrideBaseDownloadingURL加载最新的AssetBundle。
咱们回顾一下第二章的流程图,结合打包过程和Demo的代码,作进一步的说明.
打包过程2里,在Unity的游戏逻辑以前,插入了三行Java代码。
+ System.loadLibrary("main");
+ System.loadLibrary("unity");
+ System.loadLibrary("bootstrap");
mUnityPlayer = new UnityPlayer(this);
复制代码
这三行代码保证了上图中步骤1-2能在步骤3以前执行,下一行mUnityPlayer的代码即开始了步骤3的执行。步骤3以后全部的逻辑,都是已热更过的il2cpp库里的Unity Script(c#,...)了。热更部分的逻辑若是有修改,会在热更后体现,若是这部分的bug不影响下次热更,则能够经过热更修复,不然应指引用户清除本地数据,以母包热更逻辑更新到最新。因此,在方案的应用中,仍需尽可能保证热更部分的代码稳定,不能随意更改。
如前所述,Demo里没有步骤4和步骤5的相关逻辑,步骤6中Patch的准备,Demo只是简单地将全量压缩包解压,相关逻辑在Script/VersionSettor.cs文件中。准备更新目录时,应保证libil2cpp部分被解压,命名方式和Demo保持一致,而assets_bin_Data下的文件不须要解压,应保证目录结构和Demo保持一致。若是是增量更新,Patch目录下的文件应该是相对于母包的修改文件。在持续热更中,应保证在步骤7前,本地当前Patch目录的完整性(保证运行中的App还能正常执行),新的Patch应新建目录,经过硬连接的形式从当前Patch目录中提取所须要的没变化的文件,准备好后执行步骤7,重启后将老Patch目录删除. 步骤7和步骤8的代码也在Script/VersionSettor.cs文件中,样子以下
//4. tell libboostrap.so to use the right patch after reboot
string error = Bootstrap.use_data_dir(runtimePatchPath);
if (!string.IsNullOrEmpty(error))
{
messageBox.Show("use failed. path:" + zipLibil2cppPath + ", error:" + error, "ok", () => { messageBox.Close(); });
yield break;
}
//5. reboot app
yield return StartCoroutine(Restart());
复制代码
安装预编译的Apk文件,点击按钮能够切换各个版本。
打包部分
运行时部分
另外,打包的工做尽可能自动的一键化,一次化,除非你想在打包当晚集体晒月亮。另外,低成本的打包流程,你们都愿意在真机上看结果,利于产品的稳定。Demo其实提供了一套自动化的框架和脚本,理解透,化为己用,也是幸事一件。若是有更好的方式,欢迎讨论。