這是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其實提供了一套自動化的框架和腳本,理解透,化爲己用,也是幸事一件。若是有更好的方式,歡迎討論。