C#熱更新方案的選擇

前項(xiàng)目的C#熱更方案

小甜甜的C#熱更方案

前段時(shí)間 noodle 說(shuō)他把 小甜甜 項(xiàng)目中他做的 C#熱更方案 開(kāi)源了。

這個(gè)方案是一個(gè) 騷操作,不過(guò)是針對(duì) il2cpp 的,核心思想是更新 libil2cpp.so,具體細(xì)節(jié)可以參考 github主頁(yè)。

暗黑血統(tǒng)的C#熱更方案

再早一點(diǎn)的項(xiàng)目 暗黑血統(tǒng),那還是 Unity4 的時(shí)代,我們的C#熱更方案也是一個(gè) 騷操作,這里列一下要點(diǎn):

  1. Assembly-CSharp-firstpass.dllAssembly-CSharp.dll 是Unity預(yù)定的2個(gè)程序集。

  2. Assembly-CSharp-firstpass.dll 包括了加載熱更代碼的代碼,不可被熱更新,Assembly-CSharp.dll 包括主要的游戲邏輯代碼,期望可以被熱更新。

  3. 打包的時(shí)候,把 Assembly-CSharp.dll 中的代碼移動(dòng)到我們自定的 GameLogic.dll 中,并把 Assembly-CSharp.dll 清空(namespace顛倒)。

  4. 運(yùn)行的時(shí)候,Assembly-CSharp-firstpass.dll 中的代碼通過(guò) Assembly.Load 的方式去加載 GameLogic.dllGameLogic.dll 可以從服務(wù)器下載獲取,以此達(dá)到熱更新的目的。

這樣做看起來(lái)OK,但是有一個(gè)很大的限制: 預(yù)設(shè)不能掛載非firstpass目錄的腳本,原因可以參考這篇帖子。 當(dāng)然,我們可以在運(yùn)行時(shí)通過(guò) AddComponent 的方式去掛載腳本,但是這樣做限制較大。

騷操作 之所以被稱為 騷操作,就是我們可以打破這個(gè)限制:即把 Assembly-CSharp.dll 換成了 GameLogic.dll 后,也要保證預(yù)設(shè)能夠找得到原先引用的腳本。

暗黑血統(tǒng) 的做法是:在生成GameLogic.dll后,改cs文件對(duì)應(yīng)的meta文件,把dll重新定向到GameLogic.dll,重啟編輯器再打包。

screenshot1.png

在打包的時(shí)刻,預(yù)設(shè)已經(jīng)認(rèn)定了 GameLogic.dll,所以加載時(shí)就不會(huì)丟失腳本了。

當(dāng)然,這個(gè)方案依然也有局限:

  1. 必須嚴(yán)格保證 Assembly-CSharp-firstpass.dll 的穩(wěn)定,一旦出現(xiàn)了問(wèn)題,只能換包。

  2. 熱更后,如果 GameLogic.dll 新增了一個(gè)上架包中并不存在的腳本,那么掛載這個(gè)腳本的預(yù)設(shè)在加載時(shí)依然還是會(huì)出現(xiàn)腳本丟失。

總體來(lái)說(shuō),這套方案沒(méi)什么大問(wèn)題,在線上運(yùn)行良好。出現(xiàn)上面的問(wèn)題2時(shí),我們就 AddComponent 繞一下。

隨著Unity的升級(jí)換代,metadata的格式也在變化,這套依賴 改meta文件 的方案在版本兼容性上出現(xiàn)了很大問(wèn)題,最終因?yàn)殡y以維護(hù)被拋棄了。


目前的C#熱更新方案

時(shí)至今日,如果再做方案選擇,我傾向于集成 xLua。

對(duì)于Android平臺(tái)的C#熱更,我傾向于目前公司所采用的方案:自己編譯libmono.so。

我們可以在github上找到各個(gè)Unity版本對(duì)應(yīng)的 mono源碼,做如下操作:

  • 打開(kāi) image.c 文件。
  • 找到 mono_image_open_from_data_with_name 函數(shù)。
  • 截住加載 Assembly-CSharp-firstpass.dll 的邏輯,做我們自己的操作。

代碼流程如下:

MonoImage *
mono_image_open_from_data_with_name (char *data, guint32 data_len, gboolean need_copy, MonoImageOpenStatus *status, gboolean refonly, const char *name)
{   
    int datasize = 0; 

    if(name != NULL && strstr(name,"Assembly-CSharp-firstpass.dll"))
    {
        // 從我們的Patch目錄讀取我們自己的dll文件,覆蓋傳入的data  
    }

    // 解密data

    // 走原先的do_mono_image_load流程
}

這里有幾個(gè)細(xì)節(jié)要注意:

  1. Assembly-CSharp-firstpass.dllAssembly-CSharp.dll 我們都做了加密,所以這里有一個(gè)步驟是解密。

  2. mono_image_open_from_data_with_name 函數(shù)只攔截了加載 Assembly-CSharp-firstpass.dll 的操作,并未攔截 Assembly-CSharp.dll

  3. 至于 Assembly-CSharp.dll,我們打包的時(shí)候直接把他刪了,所以這里不會(huì)加載它。和 暗黑血統(tǒng) 的做法類似,我們還是通過(guò) Assembly-CSharp-firstpass.dll 中的代碼來(lái)加載它。

改完源碼,重新編譯生成新的 libmono.so,就大功告成了。

這個(gè)方案比較成熟,網(wǎng)上的文章一搜一大把。我之所以傾向于這個(gè)方案,主要有以下幾點(diǎn)考慮:

  1. 這個(gè)方案對(duì)整個(gè)項(xiàng)目的 侵入性很小,只需要替換掉 libmono.so 即可。

  2. 加載熱更代碼的代碼從 Assembly-CSharp-firstpass.dll 轉(zhuǎn)移到了 libmono.so,因此 Assembly-CSharp-firstpass.dll 也可以被熱更新了。

  3. 這個(gè)方案對(duì)團(tuán)隊(duì)人員的要求沒(méi)那么高,維護(hù)成本相對(duì)較低。

當(dāng)然,這個(gè)方案也有以下一些限制:

  1. 如果更新 Assembly-CSharp-firstpass.dll,需要重啟一次進(jìn)程。

  2. Standard Assets 或者 Plugins 目錄下的代碼可以被掛載,但是 非firstpass目錄 下的代碼不行,因?yàn)檫@里并沒(méi)有 暗黑血統(tǒng)改meta 的那一步騷操作。

重啟進(jìn)程對(duì)用戶體驗(yàn)有一點(diǎn)傷害,特別是 進(jìn)程不能被快速拉起 時(shí),可能會(huì)影響留存。不過(guò)后來(lái)我們?cè)趕dk里加了一個(gè) 秒啟 的函數(shù),現(xiàn)在重啟的代價(jià)可以忽略不計(jì)了,代碼如下:

public void doRestartApp()
{
    new Thread()
    {
        public void run()
        {
            Intent localIntent = mContext.getPackageManager().getLaunchIntentForPackage(mContext.getPackageName());
            localIntent.addFlags(67108864);
            mContext.startActivity(localIntent);
            android.os.Process.killProcess(android.os.Process.myPid());
        }
    }.start();

    finish();
}

至于 非firstpass目錄 下代碼無(wú)法掛載的問(wèn)題,我們會(huì)把需要掛載的代碼統(tǒng)一移動(dòng)到 Plugins 目錄下,因?yàn)?Assembly-CSharp-firstpass.dll 已經(jīng)可以被熱更新了。


個(gè)人主頁(yè)

本文的個(gè)人主頁(yè)鏈接:https://baddogzz.github.io/2019/12/26/CSharp-Patch/

好了,拜拜。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容