1. 程式人生 > >Android客戶端外掛化熱修復各種方案對比和最全總結

Android客戶端外掛化熱修復各種方案對比和最全總結

2016年不能扯幾句熱修復和外掛化都不好意思說自己是做 Android 的,雖然我對這個技術不怎麼感興趣,奈何業務需要也得深入的研究一下,本文記錄我對熱修復的外掛化的學習和研究。

技術背景

外掛化解決的問題

  1. 減小主包大小

  2. 不發版上新功能

  3. 獨立開發載入 A/B TEST 模組

  4. bug 修復工具

個人態度

學習這項技術是關心技術後的本質,在專案中能不用就不用,因為本身這種做法是 Google 不推薦的,RN才是今後的發展方向。但是RN效能方面的優化也很重要,這方面沒深入瞭解。

在專案中引入新技術

在專案中使用新技術,哪怕是引入一個第三方庫也要小心謹慎,確定是否確實需要。但是還是要對不瞭解的新的東西要積極學習、積極研究,這樣在引入實際生產環境後也能快速踩坑出坑。

個人認為開發不能脫離業務,不能脫離實際場景為了技術而技術,也不能害怕引入新技術,風險和效率等優點是並存的,但是要合理的控制風險。

介紹名詞

  1. 外掛化 – apk 分為宿主和外掛部分,外掛在需要的時候才載入進來

  2. 熱修復 – 更新的類或者外掛粒度較小的時候,我們會稱之為熱修復,一般用於修復bug

  3. 熱更新 – 2016 Google 的 Android Studio 推出了Instant Run 功能 同時提出了3個名詞

    “熱部署” – 方法內的簡單修改,無需重啟app和Activity。 “暖部署” – app無需重啟,但是activity需要重啟,比如資源的修改。 “冷部署” – app需要重啟,比如繼承關係的改變或方法的簽名變化等。

所以站在app開發者角度的“熱”是指在不發版的情況來實現更新,而Google提出的“熱”是指值是否需要重新啟動。 同時在開發外掛化的時候也有兩種情景,一種是外掛與宿主apk沒有互動,只是在使用者使用到的時候進行一次吊起,還有一種是與宿主有很多的互動。

從MulitiDex開始

當 Android 系統安裝一個應用的時候,有一步是對 Dex 進行優化,這個過程有一個專門的工具來處理,叫 DexOpt 。DexOpt 的執行過程是在第一次載入Dex檔案的時候執行的。這個過程會生成一個 ODEX 檔案,即 Optimised Dex。執行 ODex 的效率會比直接執行 Dex 檔案的效率要高很多。

但是在早期的 Android 系統中,DexOpt 有一個問題,DexOpt 會把每一個類的方法 id 檢索起來,存在一個連結串列結 構裡面。但是這個連結串列的長度是用一個 short 型別來儲存的,導致了方法 id 的數目不能夠超過65536個。當一個專案足夠大的時候,顯然這個方法數的上限是不夠的。儘管在新版本的 Android 系統中,DexOpt 修復了這個問題,但是我們仍然需要對低版本的 Android 系統做相容。

為了解決方法數超限的問題,需要將該dex檔案拆成兩個或多個,為此谷歌官方推出了 multidex 相容包,配合 AndroidStudio 實現了一個 APK 包含多個 dex 的功能。

MulitDex 引起的問題有:

  1. 在應用安裝到手機上的時候dex檔案的安裝是複雜的(complex)有可能會因為第二個dex檔案太大導致ANR。

  2. 使用了mulitDex的App有可能在4.0(api level 14)以前的機器上無法啟動,因為Dalvik linearAlloc bug(Issue 22586) 。

  3. 使用了mulitDex的App在runtime期間有可能因為Dalvik linearAlloc limit (Issue 78035) Crash。該記憶體分配限制在 4.0版本被增大,但是5.0以下的機器上的Apps依然會存在這個限制。

  4. 主dex被dalvik虛擬機器執行時候,哪些類必須在主dex檔案裡面這個問題比較複雜。build tools 可以搞定這個問題。但是如果你程式碼存在反射和native的呼叫也不保證100%正確。

對於 davilk 和 art 虛擬機器 Mulitdex 的不同: 1. ART模式相比原來的Dalvik,會在安裝APK的時候,使用Android系統自帶的dex2oat工具把APK裡面的.dex檔案轉化成OAT檔案。

這裡說一下羅迪的快速載入 Mulitdex 方案:art虛擬機器對dex優化需要很長時間,載入外掛dex跳過優化實現加速。跳過會影響類載入的效能。

外掛化實現方案分析對比

下面對實現外掛化需要關注的一些點,和主流外掛化框架進行了一些總結分析。

實現外掛化需要解決的技術點

  1. 資源如何載入(資源衝突問題如何解決)?

  2. 程式碼如何載入訪問訪問?

  3. 外掛的管理後臺包括的內容?

  4. 外掛的增量更新問題(非必須) ?

  5. 載入外掛中的so庫 (非必須)?

針對如上問題的解決方案

  • 針對問題1

    原理: > 資源id是在編譯時生成的,其生成的規則是0xPPTTNNNN,PP段,是用來標記apk的,預設情況下系統資源PP是01,應用程式的PP是07。TT段,是用來標記資源型別的,比如圖示、佈局等,相同的型別TT值相同,但是同一個TT值不代表同一種資源,例如這次編譯的時候可能使用03作為layout的TT,那下次編譯的時候可能會使用06作為TT的值,具體使用那個值,實際上和當前APP使用的資源型別的個數是相關聯的。NNNN則是某種資源型別的資源id,預設從1開始,依次累加。

    那麼我們要解決資源id問題,就可從TT的值開始入手,只要將每次編譯時的TT值固定,即可是資源id達到分組的效果,從而避免重複。例如將宿主程式的layout資源的TT固定為33,將外掛程式資源的layout的TT值固定為03(也可不對外掛程式的資源id做任何處理,使其使用編譯出來的原生的值), 即可解決資源id重複的問題了。

    固定資源id的TT值的辦法也非常簡單,提供一份public.xml,在public.xml中指定什麼資源型別以什麼TT值開頭即可

    還有一個方法是通過定製過的aapt在編譯時指定外掛的PP段的值來實現分組,重寫過的aapt指定PP段來實現id分組。

    • 方案一: 將外掛apk資源解壓,通過操作檔案的方式來使用,這個只是理論上可行,實際使用的時候還是有很多的問題。(主要是混淆後就懵逼了)

    • 方案二: 重寫 Context 的getResource() getAsset() 之類的方法。資源衝突需要擴充套件 aapt 實現。

    • 方案三: 打包後執行一個指令碼修改資源ID。

  • 針對問題2

    原理說明:主要就是 classloader 載入dex,代理模式就是本身宿主中有Activity,通過欺騙系統來建立Activity,欺騙系統的部分hook的有深有淺(對比DroidPlugin和Small),讓這個Activity有生命週期,而動態載入模式就是執行時動態建立並編譯一個Activity類,需要使用動態建立類的工具實現動態位元組碼操作。

    • 方案一: 簡單載入模式。

    • 方案二: Activity代理模式。

    • 方案三: 動態載入模式。

  • 針對問題3:

    • 提供外掛資訊查詢和下載,包括回滾到某一版本。

    • 管理使用外掛的apk,可以向不同版本apk 提供不同外掛。

    • MD5校檢外掛的合法性。

外掛化現有框架分析對比

框架名稱 出現時間 作者 實現簡介 已知存在問題
2012年7月 mmin18 不使用Activity採用Fragment實現
2014年底 百度工程師 任玉剛 通過代理Activity來實現外掛化
2015年8月 奇虎360 深度hook實現 不支援通知欄定製
Small 2015年底 林光亮 比較DroidPlugin輕量一點,指令碼來解決資源問題 不支援Service外掛化
ACCD 2015年4月 bunnyblue OpenAtlas 之後改為ACCD 攜程開源框架參考了這個
nuwa 2015年9月 賈吉新 通過前置相同Dex來實現熱修復
2015年11月 阿里 使用JNI實現的熱修復,針對Davilk和Art呼叫的方法不同

注意:關於存在的問題 作者只看了部分框架的,列舉的也是可能影響作者自身業務的點,僅供參考

後記

差不多找了一週的資料加上看各種文章原始碼寫完這個文章,很多地方的瞭解不是那麼深入,很多東西也是拾人牙慧,希望大家批評指正。

關於Java和Android大牛頻道

Java和Android大牛頻道是一個數萬人關注的探討Java和Android開發的公眾號,分享和原創最有價值的乾貨文章,讓你成為這方面的大牛!

我們探討android和Java開發最前沿的技術:android效能優化 ,外掛化,跨平臺,動態化,加固和反破解等,也討論設計模式/軟體架構等。由群來自BAT的工程師組成的團隊

關注即送紅包,回覆:“百度” 、“阿里”、“騰訊” 有驚喜!!!關注後可用入微信群。群裡都是來自百度阿里騰訊的大牛。

歡迎關注我們,一起討論技術,掃描和長按下方的二維碼可快速關注我們。搜尋微信公眾號:JANiubility。


公眾號:JANiubility