筆記 深入探索Android熱修復技術原理

幻海流心發表於2019-02-27

阿里電子書《深入探索Android熱修復技術原理》整理的筆記


1.熱修復技術介紹

  1. 程式碼修復兩大主要方案
    • 底層替換方案:限制較多,但時效性好,立即見效
    • 類載入方案時效性差,需要重新冷啟動才能見效,但限制少
  2. 程式碼修復底層替換方案
    • 底層替換方案是在已經載入了的類中直接替換掉原有方法.
    • 不能對原有類進行方法和欄位的增減,因為這樣將破壞原有類的結構.
    • 方法增減將導致這個類及整個Dex方法數的變化,伴隨著方法索引的變化,這樣訪問方法時就無法正常的索引到正確的方法;
    • 欄位增加或減少,所有欄位的索引都會發生變化;
    • 傳統底層替換方案,都是直接依賴修改虛擬機器方法實體中的具體欄位.依據的是Android開源版本.如果廠商修改了虛擬機器方法實體,替換機制就可能出問題;
  3. 程式碼修復類載入方案
    • 類載入方案的原理是在app重新啟動後讓Classloader去載入新的類.
    • 在app執行到一半的時候,所有需要發生變更的類都已經被載入過,Android無法對一個類進行解除安裝.如果不重啟,原來的類還在虛擬機器中,就無法載入新類.
    • 只有在下次重啟時候,在還沒有走到業務邏輯前搶先載入補丁中的新類,後續訪問才是新的類.
    • dex比較的最佳粒度,應該是在類的粒度
    • Sophix採用的也是全量合成dex的技術.可以看做是dex檔案級別的類插裝方案.Sophix對舊包與補丁包中classes.dex的順序進行了打破與重組,使得系統可以自然地識別到這個順序,以實現類覆蓋的目的
  4. 資源修復
    • 市面上很多資源熱修復方案都採用了Instant Run的實現
    • Instant Run中資源修復步驟:
      1. 構造一個新的AssetManager,通過反射呼叫addAssetPath,把這個完整的新資源包加到AssetManager中.這樣就得到一個含有所有新資源的AssetManager.
      2. 找到所有之前引用到AssetManager的地方,通過反射,將引用出替換為AssetManager.
    • Sophix資源熱修復沒有采用Instant Run的技術,而是構造了一個package id 為 0x66 的資源包,這個包裡只包含改變了的資源項,然後直接在原有AssetManager中addAssetPath這個包即可.無需變更AssetManager物件的引用.
      1. Sophix構造的補丁包的 package id 為0x66,不與已經載入的 0x7f衝突,所以直接加入到已有的AssetManager中可以直接使用.
      2. Sophix資源補丁包中,只包含新增資源,以及原有內容發生了改變的資源.
  5. SO庫修復:本質上是對native方法的修復和替換

Sophix採用的是類似類修復反射注入方式,把補丁so庫的路徑插入到nativeLibraryDirectories陣列的最前面, 這樣載入so庫的時候就是補丁so庫而不是原來的so庫

2.程式碼熱修復技術

  1. 底層熱替換原理
    • Android的java執行環境,在4.4以下用的是dalvik虛擬機器,4.4以上是art虛擬機器.
    • 在各種Android熱修復方案中,Andfix即時生效.Andfix採用的方法是,在已經載入了的類中直接在native層替換掉所有方法,是在原來的類的基礎上進行修改的.
    • 以art,Android6.0為例,每一個Java方法在art中都對應著一個ArtMethod物件,ArtMethod記錄了這個Java方法的所有資訊,包括所屬類,訪問許可權,程式碼執行地址等.
    • Andfix會將一箇舊Java方法對應的ArtMethod例項中的所有欄位值替換為新方法的值,這樣所有執行到舊方法的地方,都會取得新方法的執行入口,所屬class,方法索引,所屬dex.像呼叫舊方法一樣執行了新方法的邏輯.
  2. 底層熱替換相容性根源
    • 市面上幾乎所有的native替換替換,都是寫死了ArtMethod結構體
    • 寫死的ArtMethod結構和Android開源版本中完全一致,但各個廠家可以對ArtMethod進行修改,那麼在修改過的裝置上,市面上的native替換方案(將方案中寫死了的ArtMethod關聯的新方法的屬性賦值到裝置中的ArtMethod例項)就會出現問題,因為兩個ArtMethod中相同欄位的索引不同
  3. 突破底層熱替換相容問題
    • native層面替換,實質是替換ArtMethod例項的所有欄位.
    • 只要把ArtMethod作為整體進行替換,即可解決相容問題.
    • ArtMethod例項之間,是緊密線形排列的,所以一個ArtMethod的大小,就是其相鄰的兩個方法對應的ArtMethod例項的起始地址的差值.
  4. 包括Sophix在內的底層替換方案,都只能支援方法的替換,不支援補丁類中增減方法和欄位
    • 補丁類中增減方法,會導致這個類及整個dex方法數的變化,方法數的變化伴隨方法索引的變化,這樣在呼叫方法時無法正常的所引導正確的方法.
    • 補丁類中增減欄位,也會導致所有欄位的索引發生變化.
  5. 你說不知的Java
    • 內部類在編譯期會被編譯為根外部類一樣的頂級類;
    • 非靜態內部類持有外部類的引用,靜態內部類不持有外部類的引用.所以android效能優化中建議自定義Handler的實現儘量使用靜態內部類,防止外部類Activity類不能被回收導致記憶體洩漏.
      自定義Handler使用靜態內部類避免記憶體洩漏
    • 內部類和外部類之間,訪問彼此的private屬性及方法,編譯期間:
      • 外部類訪問內部類的私有成員及方法,編譯期間自動為內部類生成access&**方法
      • 內部類訪問外部類的private屬性及方法,編譯期間也會生成access&**方法提供給內部類
    • 同一個類及其內部類,如果老程式碼沒有訪問對方的私有屬性/方法,新程式碼有訪問對方的私有屬性/方法,如果不能避免生成access&的生成,就會導致方法數的變化,導致熱修復失敗.避免生成access&方法需要:
      • 外部類所有的屬性及方法改為public或protected;
      • 內部類所有的屬性及方法改為public或protected;
    • 在編譯期間,根據匿名內部類在外部類中出現的先後順序,匿名內部類的名稱依次累加:外部類名稱&數字
      • 外部類名稱是OutClass,其中對應的內部類在編譯期間的名稱依次是:OutClass&1,OutClass&2,—–
      • 為了實現熱修復,外部類應該極力避免新增及減少匿名內部類;
      • 除非是新增匿名內部類到外部類的尾部,不會影響之前新增過的匿名內部類的名稱,不然會導致熱修復失效;
    • Java原始型別:double、float、byte、short、int、long、char、boolean
      • 如果一個常量的型別是Java原始型別,或String,為了優化效能,應該用static final修飾;
      • static final 引用型別,沒有任何優化效果.
      • 因為 static final 修飾的原始型別及String常量,是在所屬類的初始化時賦值,直接在記憶體中讀取;
      • 而 static final 引用型別常量,初始化是在clinit方法中,本質上是通過sget-object指令去獲取值,從虛擬機器執行效能上無任何優化;
  6. 市面上的冷啟動類載入實現方案
    • 1:採用dex插樁的方式,單獨放一個幫助類在獨立的dex中讓其他類呼叫.最後載入補丁dex得到dexFile物件,將dexFile作為引數構建一個Element物件插入到dexElements陣列最前面
    • 2:提供dex差量包,整體替換dex的方案:差量patch.dex和應用的classes.dex合成完整dex.載入完整dex得到dexFile物件,作為引數構建一個Element物件,然後整體替換掉舊的dexElements陣列
    • 1的缺點是:Dalvik下影響類載入效能,Art下類地址寫死,導致必須包含父類及引用,補丁包很大
    • 2的缺點是:dex的合併,記憶體消耗在 vm heap 上,容易導致OOM,合併失敗
  7. Sophix採用的程式碼修復冷啟動方案
    • Dalvik下使用全量Dex方案;
    • Art下本質上虛擬機器已經支援多dex的載入,只要把補丁dex作為主dex(classes)即可
  8. Sophix在Dalvik下全量Dex方案思路
    • 基線包dex裡面,去掉補丁包dex中包含的class;這樣補丁+去除了補丁中包含類的基線包,就等於新app中所有類;
    • Sophix並沒有把某個class的所有資訊從基線dex中移除,僅僅移除了定義的入口,讓解析基線dex時候找不到這個class的定義即可;這樣不會導致dex的各個部分都發生變化,防止大量調整offset.
    • 只要把所有的dex都load進去,單個dex中不存在的類就可以在執行期間在其他dex中找到.補丁中的類和基線中的類可以互相訪問到

3.資源熱修復技術

Android資源的熱修復,就是在app不重新安裝的情況下,利用下發的補丁包直接更新app中的資源
Sophix的資源熱修復方案

1:構造一個 package id 為0x66的資源包,這個包裡只含有變更的資源,以及新增資源;
2:然後直接在原有AssetManager上呼叫addAssetPath加入這個資源包即可;

因為我們補丁包的id和已經載入的0x7f衝突,所以直接加入原有AssetManager即可直接使用
複製程式碼

4.SO庫熱修復技術

相關文章