阿里電子書《深入探索Android熱修復(fù)技術(shù)原理》整理的筆記
1.熱修復(fù)技術(shù)介紹
-
代碼修復(fù)兩大主要方案
- 底層替換方案:限制較多,但時效性好,立即見效
- 類加載方案時效性差,需要重新冷啟動才能見效,但限制少
-
代碼修復(fù)底層替換方案
- 底層替換方案是在已經(jīng)加載了的類中直接替換掉原有方法.
- 不能對原有類進行方法和字段的增減,因為這樣將破壞原有類的結(jié)構(gòu).
- 方法增減將導(dǎo)致這個類及整個Dex方法數(shù)的變化,伴隨著方法索引的變化,這樣訪問方法時就無法正常的索引到正確的方法;
- 字段增加或減少,所有字段的索引都會發(fā)生變化;
- 傳統(tǒng)底層替換方案,都是直接依賴修改虛擬機方法實體中的具體字段.依據(jù)的是Android開源版本.如果廠商修改了虛擬機方法實體,替換機制就可能出問題;
-
代碼修復(fù)類加載方案
- 類加載方案的原理是在app重新啟動后讓Classloader去加載新的類.
- 在app運行到一半的時候,所有需要發(fā)生變更的類都已經(jīng)被加載過,Android無法對一個類進行卸載.如果不重啟,原來的類還在虛擬機中,就無法加載新類.
- 只有在下次重啟時候,在還沒有走到業(yè)務(wù)邏輯前搶先加載補丁中的新類,后續(xù)訪問才是新的類.
- dex比較的最佳粒度,應(yīng)該是在類的粒度
- Sophix采用的也是全量合成dex的技術(shù).可以看做是dex文件級別的類插裝方案.Sophix對舊包與補丁包中classes.dex的順序進行了打破與重組,使得系統(tǒng)可以自然地識別到這個順序,以實現(xiàn)類覆蓋的目的
-
資源修復(fù)
- 市面上很多資源熱修復(fù)方案都采用了Instant Run的實現(xiàn)
- Instant Run中資源修復(fù)步驟:
- 構(gòu)造一個新的AssetManager,通過反射調(diào)用addAssetPath,把這個完整的新資源包加到AssetManager中.這樣就得到一個含有所有新資源的AssetManager.
- 找到所有之前引用到AssetManager的地方,通過反射,將引用出替換為AssetManager.
-
Sophix資源熱修復(fù)沒有采用Instant Run的技術(shù),而是構(gòu)造了一個package id 為 0x66 的資源包,這個包里只包含改變了的資源項,然后直接在原有AssetManager中addAssetPath這個包即可.無需變更AssetManager對象的引用.
- Sophix構(gòu)造的補丁包的 package id 為0x66,不與已經(jīng)加載的 0x7f沖突,所以直接加入到已有的AssetManager中可以直接使用.
- Sophix資源補丁包中,只包含新增資源,以及原有內(nèi)容發(fā)生了改變的資源.
- SO庫修復(fù):本質(zhì)上是對native方法的修復(fù)和替換
Sophix采用的是類似類修復(fù)反射注入方式,把補丁so庫的路徑插入到nativeLibraryDirectories數(shù)組的最前面, 這樣加載so庫的時候就是補丁so庫而不是原來的so庫
2.代碼熱修復(fù)技術(shù)
-
底層熱替換原理
- Android的java運行環(huán)境,在4.4以下用的是dalvik虛擬機,4.4以上是art虛擬機.
- 在各種Android熱修復(fù)方案中,Andfix即時生效.Andfix采用的方法是,在已經(jīng)加載了的類中直接在native層替換掉所有方法,是在原來的類的基礎(chǔ)上進行修改的.
- 以art,Android6.0為例,每一個Java方法在art中都對應(yīng)著一個ArtMethod對象,ArtMethod記錄了這個Java方法的所有信息,包括所屬類,訪問權(quán)限,代碼執(zhí)行地址等.
- Andfix會將一個舊Java方法對應(yīng)的ArtMethod實例中的所有字段值替換為新方法的值,這樣所有執(zhí)行到舊方法的地方,都會取得新方法的執(zhí)行入口,所屬class,方法索引,所屬dex.像調(diào)用舊方法一樣執(zhí)行了新方法的邏輯.
-
底層熱替換兼容性根源
- 市面上幾乎所有的native替換替換,都是寫死了ArtMethod結(jié)構(gòu)體
- 寫死的ArtMethod結(jié)構(gòu)和Android開源版本中完全一致,但各個廠家可以對ArtMethod進行修改,那么在修改過的設(shè)備上,市面上的native替換方案(將方案中寫死了的ArtMethod關(guān)聯(lián)的新方法的屬性賦值到設(shè)備中的ArtMethod實例)就會出現(xiàn)問題,因為兩個ArtMethod中相同字段的索引不同
-
突破底層熱替換兼容問題
- native層面替換,實質(zhì)是替換ArtMethod實例的所有字段.
- 只要把ArtMethod作為整體進行替換,即可解決兼容問題.
- ArtMethod實例之間,是緊密線形排列的,所以一個ArtMethod的大小,就是其相鄰的兩個方法對應(yīng)的ArtMethod實例的起始地址的差值.
-
包括Sophix在內(nèi)的底層替換方案,都只能支持方法的替換,不支持補丁類中增減方法和字段
- 補丁類中增減方法,會導(dǎo)致這個類及整個dex方法數(shù)的變化,方法數(shù)的變化伴隨方法索引的變化,這樣在調(diào)用方法時無法正常的所引導(dǎo)正確的方法.
- 補丁類中增減字段,也會導(dǎo)致所有字段的索引發(fā)生變化.
-
你說不知的Java
- 內(nèi)部類在編譯期會被編譯為根外部類一樣的頂級類;
-
非靜態(tài)內(nèi)部類持有外部類的引用,靜態(tài)內(nèi)部類不持有外部類的引用.所以android性能優(yōu)化中建議自定義Handler的實現(xiàn)盡量使用靜態(tài)內(nèi)部類,防止外部類Activity類不能被回收導(dǎo)致內(nèi)存泄漏.
自定義Handler使用靜態(tài)內(nèi)部類避免內(nèi)存泄漏 - 內(nèi)部類和外部類之間,訪問彼此的private屬性及方法,編譯期間:
- 外部類訪問內(nèi)部類的私有成員及方法,編譯期間自動為內(nèi)部類生成access&**方法
- 內(nèi)部類訪問外部類的private屬性及方法,編譯期間也會生成access&**方法提供給內(nèi)部類
- 同一個類及其內(nèi)部類,如果老代碼沒有訪問對方的私有屬性/方法,新代碼有訪問對方的私有屬性/方法,如果不能避免生成access&的生成,就會導(dǎo)致方法數(shù)的變化,導(dǎo)致熱修復(fù)失敗.避免生成access&方法需要:
- 外部類所有的屬性及方法改為public或protected;
- 內(nèi)部類所有的屬性及方法改為public或protected;
- 在編譯期間,根據(jù)匿名內(nèi)部類在外部類中出現(xiàn)的先后順序,匿名內(nèi)部類的名稱依次累加:外部類名稱&數(shù)字
- 外部類名稱是OutClass,其中對應(yīng)的內(nèi)部類在編譯期間的名稱依次是:OutClass&1,OutClass&2,-----
- 為了實現(xiàn)熱修復(fù),外部類應(yīng)該極力避免新增及減少匿名內(nèi)部類;
- 除非是新增匿名內(nèi)部類到外部類的尾部,不會影響之前添加過的匿名內(nèi)部類的名稱,不然會導(dǎo)致熱修復(fù)失效;
- Java原始類型:double喂很、float、byte、short、int疫蔓、long萧吠、char、boolean
- 如果一個常量的類型是Java原始類型,或String,為了優(yōu)化性能,應(yīng)該用static final修飾;
- static final 引用類型,沒有任何優(yōu)化效果.
- 因為 static final 修飾的原始類型及String常量,是在所屬類的初始化時賦值,直接在內(nèi)存中讀取;
- 而 static final 引用類型常量,初始化是在clinit方法中,本質(zhì)上是通過sget-object指令去獲取值,從虛擬機運行性能上無任何優(yōu)化;
-
市面上的冷啟動類加載實現(xiàn)方案
- 1:采用dex插樁的方式,單獨放一個幫助類在獨立的dex中讓其他類調(diào)用.最后加載補丁dex得到dexFile對象,將dexFile作為參數(shù)構(gòu)建一個Element對象插入到dexElements數(shù)組最前面
- 2:提供dex差量包,整體替換dex的方案:差量patch.dex和應(yīng)用的classes.dex合成完整dex.加載完整dex得到dexFile對象,作為參數(shù)構(gòu)建一個Element對象,然后整體替換掉舊的dexElements數(shù)組
- 1的缺點是:Dalvik下影響類加載性能,Art下類地址寫死,導(dǎo)致必須包含父類及引用,補丁包很大
- 2的缺點是:dex的合并,內(nèi)存消耗在 vm heap 上,容易導(dǎo)致OOM,合并失敗
-
Sophix采用的代碼修復(fù)冷啟動方案
- Dalvik下使用全量Dex方案;
- Art下本質(zhì)上虛擬機已經(jīng)支持多dex的加載,只要把補丁dex作為主dex(classes)即可
-
Sophix在Dalvik下全量Dex方案思路
- 基線包dex里面,去掉補丁包dex中包含的class;這樣補丁+去除了補丁中包含類的基線包,就等于新app中所有類;
- Sophix并沒有把某個class的所有信息從基線dex中移除,僅僅移除了定義的入口,讓解析基線dex時候找不到這個class的定義即可;這樣不會導(dǎo)致dex的各個部分都發(fā)生變化,防止大量調(diào)整offset.
- 只要把所有的dex都load進去,單個dex中不存在的類就可以在運行期間在其他dex中找到.補丁中的類和基線中的類可以互相訪問到
3.資源熱修復(fù)技術(shù)
Android資源的熱修復(fù),就是在app不重新安裝的情況下,利用下發(fā)的補丁包直接更新app中的資源
Sophix的資源熱修復(fù)方案
1:構(gòu)造一個 package id 為0x66的資源包,這個包里只含有變更的資源,以及新增資源;
2:然后直接在原有AssetManager上調(diào)用addAssetPath加入這個資源包即可;
因為我們補丁包的id和已經(jīng)加載的0x7f沖突,所以直接加入原有AssetManager即可直接使用
4.SO庫熱修復(fù)技術(shù)
無