Android 熱修復 - 各框架原理學習及對比

GoileoLee發表於2017-12-28

轉載請註明出處:juejin.im/post/5a4462…

寫在開頭

從15年開始各技術大佬們開始研究熱修復技術,並陸續開源了許多的熱修復框架。如 Jasonross 的 Nuwa,美團的 Robust,阿里的 Andfix,騰訊的 Tinker 等等...均是Android 前輩們夜以繼日的成果。而現在熱修復被廣泛地應用於Android 應用和遊戲,運用並理解熱修復框架在面試中也是加分項。所以,趕緊學起來吧...

本文以Tinker 作為學習物件,主要講述各開源框架的對比和記錄Tinker 的Demo 整合過程。

熱修復框架原理總結

這一節篇幅較長,主要是用自己的話來總結各熱修復框架的實現原理。如果想看Tinker接入實現的同學可進入下一章節。Android 熱修復 - Tinker 實現及踩過的坑

Nuwa 實現原理:

最早看到熱修復框架的相關文章就是Qzone官方的文章,但是Qzone熱修復技術的實現程式碼並沒有開源。不過GitHub上有一開源的熱修復框架Nuwa,實現原理和其相似。這裡我們以Qzone為例進行分析。

Qzone的實現原理是生成差分dex檔案,將patch.dex插到dexElements的最前面。但patch.dex中的類patchA.java和classes.dex中的類classesB.java會相互引用,如果這兩個類所在的patch.dex和classes.dex不是同一個檔案就會報錯。

疑惑的Qzone技術大佬發現classes.dex和classes2.dex也不是同一個檔案,為啥不報錯?於是繼續查,發現了這個判斷

if(!fromUnverifiedConstant && IS_CLASS_FLAG_SET(referrer, CLASS_ISPREVERIFIED)){

如果被引用者(也就是classesB.java)這個類被打上了CLASS_ISPREVERIFIED標誌,那麼就會進行dex的校驗。校驗不過就報錯了。

那麼這個標誌是什麼時候被打上去的? 在apk安裝的時候,虛擬機器會將dex優化成odex後才拿去執行。在這個過程中會對所有class進行校驗。

怎麼校驗的? 假設classesB.java類在它的static方法,private方法,建構函式,override方法中直接引用到classesC.java類。如果classesB.java類和classesC.java類在同一個dex中,那麼classesB.java類就會被打上CLASS_ISPREVERIFIED標記,被打上這個標記的類不能引用其他dex中的類,否則就會報錯。所以要防止類被打上CLASS_ISPREVERIFIED的標誌。

// 校驗引用者 ClassB 和被引用者 PatchA 的 dex 是否相同,不同則報錯
if(referrer->pDvmDex != resClassCheck->pDvmDex &&
    resClassCheck->classLoader != NULL)
複製程式碼

如何防止類被打上CLASS_ISPREVERIFIED的標誌? Common類會被打包成單獨的hack.dex,這樣當安裝apk的時候,classes.dex內的類都會引用一個在不相同dex中的Common類,這樣就防止了類被打上CLASS_ISPREVERIFIED的標誌了,只要沒被打上這個標誌的類都可以進行打補丁操作。

優點:

  • 開發透明,簡單;
  • 熱修復成功率高。

缺點:

  • 在Art 上表現為補丁包較大;
  • 在Dalvik 上效能消耗較大。

Robust實現原理:

Robust 外掛對每個產品程式碼的每個函式都在編譯打包階段自動的插入了一段程式碼。通過判斷 if(changeQuickRedirect != null) 來確定是否進行熱修復,當 changeQuickRedirect 不為 null 時,呼叫 patch.dex 中同名類的同名方法達到 hotfix 的目的。

如何呼叫呢? 生成的patch.dex 中有兩個主要類,PatchesInfoImpl.java 和修復後的同名類 APatch.java。客戶端拿到patch.dex 後,用DexClassLoader 載入patch.dex,反射拿到PatchesInfoImpl.java 這個 class。並建立這個class 的一個物件。然後通過這個物件知道被替換的是誰,給它的變數changeQuickRedirect 賦值為 patch.dex 中的 APatch 的物件,這樣就會去執行補丁包中的方法了。

大致流程圖如下所示:A_old 表示未被修復的原有類,A_new 表示已修復的新類。

Robust 原理流程圖

優點

  • 相容性高,開發透明;
  • 實時生效。

缺點

  • 會增大方法數,影響執行效率;
  • 暫不支援 so 檔案和資源的替換。

Andfix 實現原理:

Andfix 採用的方法是,在已經載入了的類中直接在 native 層替換掉原有方法,是在原來類的基礎上進行修改的。其核心在於 replaceMethod 函式,所以只支援方法替換,對於方法的增刪,資源更新,so 檔案更新及類和屬性的替換等都是不支援的。

Andfix 的實現偏 native 層,筆者能力有限,其具體實現過程,就不妄加總結了。 更多實現細節請看 Andfix 官網文章

優點

  • 立即生效,消耗低;
  • 補丁包較小。

缺點

  • 僅支援方法替換;
  • 相容性不佳,對部分機型暫不支援。

Tinker 實現原理:

在 App 執行到一半的時候,所有需要發生變更的 Class 已經被載入過了,在Android 上是無法對一個 Class 進行解除安裝的。而 Tinker 的方案,都是讓 Classloader 去載入新的類。如果不重啟,原來的類還在虛擬機器中,就無法載入新類。因此,只有在下次重啟的時候,在還沒走到業務邏輯之前搶先載入補丁中的新類,這樣後續訪問這個類時,就會 Resolve 為新的類。從而達到熱修復的目的。

Tinker 的實現過程更像是在 Qzone 熱修復方案上做優化。核心點是效能最優,消耗最低。

經 Tinker 開發人員調研,Qzone 的方案最大挑戰在於效能,即Dalvik平臺存在插樁導致的效能損耗,Art平臺由於地址偏移問題導致補丁包可能過大的問題。為了避免這兩個問題,根據 Instant Run 的全量替換新的 Dex 的思路,於是決定將新舊兩個Dex 的差異放到補丁包中。

經過調研,BsDiff 演算法對 Dex 支援效果不太好,所以,Tinker 開發團隊人員自研了 DexDiff 演算法。

最終, BsDiff 載入 so 和部分資原始檔,DexDiff 載入 Dex檔案,以達到效能最優。但是這個方案也有缺點,就是佔用 ROM 較大。好吧!現在手機記憶體都不小,多幾十 M 可以接受。

優點

  • 補丁包較小,消耗較小;
  • 開發透明,文件豐富。

缺點

  • 佔用 ROM 較大;
  • 需要重啟才能生效。

資料對比:

Type Nuwa Robust Andfix Tinker
Company Null Meituan Alibaba Tencent
開發時間 2015 2016 2015 2016
替換類 X X
替換So X X X
替換資源 X X
即時生效 X X
成功率 較高 最高 一般 較高
介面文件 ★★ ★★ ★★ ★★★★

寫在後頭

單就熱修復線上 APP 某一處或多處 bug 來說,Andfix能做到即時修復,且操作簡單,不用生成較多的 patch.dex 包,能輕鬆解決緊急問題。

但對於如非緊急 bug 的修復及小版本的釋出,對即時生效性要求不高的情況,Tinker 支援的替換內容較豐富,更勝一籌。

阿里將 Andfix 升級為商業版 SDK Sophix;騰訊將 Tinker 升級為 Bugly。 Sophix 不但支援即時修復,還支援再次啟動修復類、so 檔案、資源等。作為商用 APP 整合 Sophix 是很好的選擇。 但 Tinker 的開源,為其帶來了大量的使用者和測試者,除此以外還與各大手機廠商建立聯絡,使得各廠商在系統定製時也會考慮是否影響熱修復的問題。所以 Tinker 的相容性可見一斑。

總的來說,各有千秋,各需所需吧。 我們這裡以 Tinker 為學習物件,接下來先讓 Tinker-Demo 跑起來,看一下實際效果。 Android 熱修復 - Tinker 實現及踩過的坑

推薦閱讀:Android 熱修復 - Tinker 實現及踩過的坑
Amigo學習(一)解決使用中遇到的問題

記錄在此,僅為學習!
感謝您的閱讀!歡迎指正!
歡迎加入 Android 技術交流群,群號:155495090。

相關文章