Unity Android il2cpp的完美熱更解決方案

LuDong發表於2019-03-04

1. 簡介

這是Unity Android il2cpp的完美熱更解決方案的Demo(Git地址)的說明。

和現有的熱更解決方案不同的是,他不會引入多餘的語言(只是UnityScript,c#…),對Unity程式設計和編碼沒有任何限制。你可以在預置和場景裡的GameObject上新增任何的Compnents元件,需要序列化的和不需要序列化的,他們都是可以熱更的,也不需要做額外的標記處理。簡而言之,在此方案下,Unity的所有資源和指令碼,都是可以熱更的。

本文接下來將介紹如何去製作熱更檔案和如何應用這些熱更檔案。為了簡化Demo的設計,Demo包含的熱更檔案會事先以全量更新的方式製作好,一起打到了Apk裡面。具體到專案中熱更檔案得放伺服器,正式上線得放CDN,以增量更新的方式搗鼓出和文中一樣的目錄結構就OK了。

2. 方案總覽

Unity在以il2cpp方式匯出Android工程(或者Apk檔案)的時候,程式碼會被編譯成libil2cpp,而相關的資源、配置和序列化資料會以他們各自的格式匯出到android的assets目錄(assets/bin/Data)。這兩部分,libil2cpp和assets目錄,必須匹配(即需要在同一次打包中提取,可能有的變了,有的沒變,增量方式只提取變化的部分)才能正常工作,不然Unity會在啟動時崩潰。本方案就是熱更這兩部分。

熱更的正式流程如下圖.

執行時流程圖

流程說明:

  • 步驟1,在Unity的邏輯之前,libbootstrap會檢查本地是否有Patch. Apk安裝後,沒應用過任何熱更,本地是不會有Patch檔案的,走no流程。如果熱更過,則會有Patch目錄,走yes流程。Patch目錄如何準備,後面會將到。

  • 步驟2,載入Patch目錄對應架構(arm/x86)的libil2cpp庫,並應用assets目錄的更新檔案。

  • 步驟3,開始Unity的流程,進入Unity第一個場景,並執行相關的Unity Script,一般是C#,我們都以C#舉例。

  • 步驟4,檢查服務端是否有新的patch,這步demo沒有演示,需要自己實現。

  • 步驟5,下載新的patch,這步demo也沒有演示,需要自己實現

  • 步驟6,根據規則準備patch目錄,詳細規則會在後面描述。在Demo中只是將全量更新包解壓,全量更新包打包的時候目錄結構就是對的,所以不需要做其他的處理。

  • 步驟7,呼叫libbootstrap的介面設定patch目錄,因為libil2cpp已經載入進程式,所以需要重啟APP,從新的patch目錄載入patch。這步Demo中有設定patch目錄的例子。

  • 步驟8,重啟APP,Demo提供了純C#程式碼。

  • 步驟9,沒有新的Patch,就正常進入遊戲了。

流程裡更詳細的描述和如何生成Patch檔案,見第三章。

3. Demo詳述

3.1. Demo目錄結構

工程所有檔案均置於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檢視。下面會詳細描述打包過程和如何應用熱更檔案。

3.2. 打包過程

所有的打包邏輯在檔案EditorAndroidBuilder.cs裡。展開主選單AndroidBuilder, 可以看到有5步,為了和熱更啟動流程區分,我們就叫他過程。

  • 過程1:以il2cpp的方式,匯出Gradle Android工程。選擇Gradle Android工程,而不是ADT Android工程,只是因為Unity2018不再支援ADT方式。Demo並不依賴AndroidStudio,只是匯出的Android工程目錄結構是以Gradle的方式註釋,之後的構建步驟都是呼叫原始JDK/SDK的方式。Demo這部分的程式碼可以複用,但需要根據專案需求做一些修改。
  • 過程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。

3.3. 執行時應用熱更檔案

我們回顧一下第二章的流程圖,結合打包過程和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());
複製程式碼

4. Verify 和 Build

4.1. Verify

安裝預編譯的Apk檔案,點選按鈕可以切換各個版本。

Apk連結

4.2. Build

依賴

  • Unity (我用Unity2017/Unity2018)
  • JDK.(我用JDK1.8)
  • Android SDK.(我用Android SDK platform 23 和 build tools 26.0.2(低於26.0.2,Unity2018不支援),最好是這兩個版本,不然得重新下載)
  • Android NDK r13b. (Unity il2cpp原來只支援這個版本,現在不知道放開沒,反正這個版本沒問題)
  • Git

Build指引

    1. 在Unity中(Edit->Preference->External tools)設定好 JDK/SDK/NDK 路徑,打包程式碼裡會從Unity中讀取。
    1. 如果你的Android SDK的build tools版本不是26.0.2,需要修改程式碼 AndroidBuilder.cs,第14行.
    1. 如果你使用的Android platform不是android-23,修改程式碼 AndroidBuilder.cs,第15行.
    1. 出母包,執行選單 AndroidBuilder->Run Step 1, 2, 4, 5 for base version, 成功後會彈出檔案管理器顯示apk所在的目錄.
    1. 一般來說你不需要打Patch檔案,如果要打,用git checkout version1或version2,執行選單 AndroidBuilder->Run Step 1-4 for Patch Version。PrebuildPatches目錄下的相應檔案會被更新。

5. 剩下的工作和建議

打包部分

  • 設定部分需要根據專案實際做修改。
  • 熱更檔案的增量版本化管理。

執行時部分

  • 檢查新版本和下載熱更檔案。
  • 持續增量更新的Patch目錄的準備。
  • 用Asset Bundle管理資源。

另外,打包的工作儘量自動的一鍵化,一次化,除非你想在打包當晚集體曬月亮。另外,低成本的打包流程,大家都願意在真機上看結果,利於產品的穩定。Demo其實提供了一套自動化的框架和指令碼,理解透,化為己用,也是幸事一件。如果有更好的方式,歡迎討論。

相關文章