Home

Awesome

1. 简介

这是Unity Android APP il2cpp热更完美解决方案的Demo(Git地址)的说明。

和现有的热更解决方案不同的是,他不会引入多余的语言(只是UnityScript,c#...),对Unity程序设计和编码没有任何限制。你可以在预置和场景里的GameObject上添加任何的Compnents组件,需要序列化的和不需要序列化的,他们都是可以热更的,也不需要做额外的标记处理。简而言之,在此方案下,Unity的所有资源和脚本,都是可以热更的。

本文接下来将介绍如何去制作热更文件和如何应用这些热更文件。为了简化Demo的设计,Demo包含的热更文件会事先以全量更新的方式制作好,一起打到了Apk里面。具体到项目中热更文件得放服务器,正式上线得放CDN,以增量更新的方式捣鼓出和文中一样的目录结构就OK了。

Demo代码适用Unity2018-?,2017需要自己看版本历史回退Zip库。

2. 方案总览

Unity在以il2cpp方式导出Android工程(或者Apk文件)的时候,代码会被编译成libil2cpp,而相关的资源、配置和序列化数据会以他们各自的格式导出到android的assets目录(assets/bin/Data)。这两部分,libil2cpp和assets目录,必须匹配(即需要在同一次打包中提取,可能有的变了,有的没变,增量方式只提取变化的部分)才能正常工作,不然Unity会在启动时崩溃。本方案就是热更这两部分。

热更的正式流程如下图.

运行时流程图

流程说明:

流程里更详细的描述和如何生成Patch文件,见第三章。

3. Demo详述

3.1. Demo目录结构

工程所有文件均置于AndroidIl2cppPatchDemo目录下。各文件目录说明如下表。

名字说明
Editor/AndroidBuilder.cs这个文件包含所有从导出Android工程,到输出Patch和生成Apk安装文件的代码。
Editor/Exe/zip.exezip压缩工具,用来将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查看。下面会详细描述打包过程和如何应用热更文件。

3.2. 打包过程

所有的打包逻辑在文件Editor\AndroidBuilder.cs里。展开主菜单AndroidBuilder, 可以看到有5步,为了和热更启动流程区分,我们就叫他过程。

主菜单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。

3.3. 运行时应用热更文件

我们回顾一下第二章的流程图,结合打包过程和Demo的代码,做进一步的说明.

运行时流程图

打包过程2里,需要在UnityPlayerActivity.java文件头导入一个库,在Unity的游戏逻辑之前,插入了一行Java代码。

        import android.view.WindowManager;
+       import io.github.noodle1983.Boostrap;
+       Boostrap.InitNativeLibBeforeUnityPlay(getApplication().getApplicationContext().getFilesDir().getPath());
        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());

4. Verify 和 Build

4.1. Verify

安装预编译的Apk文件,点击按钮可以切换各个版本。

release版本

4.2. Build

依赖

Build指引

5. 剩下的工作和建议

打包部分

运行时部分

另外,打包的工作尽量自动的一键化,一次化,除非你想在打包当晚集体晒月亮。另外,低成本的打包流程,大家都愿意在真机上看结果,利于产品的稳定。Demo其实提供了一套自动化的框架和脚本,理解透,化为己用,也是幸事一件。如果有更好的方式,欢迎讨论。

6. 许可

MIT license.

7.主要贡献

8.调试和遗留问题

9.支持

10.随缘

PayPal:https://www.paypal.me/noodle1983

Or Alipay:avatar Or Wechat:avatar