我將熱修復原理落地實踐MyHotFix
1.熱修復技術介紹
1.1 什么是熱修復
為了修復剛發版時出現的緊急bug,無需重新發版!
1.2 技術積淀
手淘基于Xposed進行改進,產生針對Android Dalvik虛擬機的Java Method Hook技術的Dexposed。
支付寶提出AndFix方案,可以做到在Dalvik和Art全平臺兼容的即時修復
阿里百川結合手淘實際使用AndFix的經驗,解耦后推出HotFix方案
2017手淘聯合阿里云推出Sophix熱修復方案
其余著名熱修復方案有:
QQ空間超級補丁、微信Tinker
餓了么Amigo
美團Robust
360RePlugin
滴滴出行VirtualAPK
uwa
2.代碼熱修復技術
2.1底層熱替換原理
AndFix:由補丁類的classLoader加載補丁類,在native層針對不同Android架構中的不同的ArtMethod結構調用對應的replaceMethod方法按照定義好的ArtMethod結構一一替換方法的所有信息如所屬類、訪問權限、代碼內存地址等。
穩定性較差,會受到國內ROM廠商對ArtMethod結構更改的影響,所以這正是AndFix不支持很多機型的原因。
Sophix:由補丁類的classLoader加載補丁類,在native層直接memcpy(smeth,dmth,sizeof(ArtMethod))替換整個artMethod的結構。初始化類時會為這個類分配空間,AllocArtMethodArray會緊挨著的new出來放入art中的方法數組中。通過計算輔助類的前后兩個方法的起始地址就可以計算出artMethod結構的大小了。
注:補丁類初始化時,也會分配自己的artMethod空間,拿這個修復過的新ArtMethod去替換舊ArtMethod的內容,不用管ArtMethod的結構。穩定性大大提高!
猜測:由于補丁類加載是從dex中加載,故替換后的ArtMethod的方法入口首先應該是dexCode解釋執行,同步被優化成oat機器碼,下次執行時就執行oat機器碼入口了。
- 訪問權限問題
1.方法調用時權限檢查
得益于在安裝時優化成oat文件時,已經校驗過了。所以對同類的方法進行調用時,不會再進行權限檢查。
2.同包名下權限問題
同包名下調用熱修復之后的方法,會再次權限檢查,在native中的IsInSamePackage方法中判斷兩個類的classLoader是否相同,否則IllegalAcessError,因為補丁類是由補丁classLoader加載的,所以解決方法時,反射修改加載之后的補丁類的classLoader字段為舊classLoader。 - 反射調用非靜態方法產生的問題(依賴冷啟動解決)
反射調用非靜態方法時,會調用底層的InvokeMethod,會調用VerifyObjectIsClass來判斷調用方法的對象是否是方法所屬類的實例,然而由于熱替換的方法類還是執行補丁類,所以校驗失敗。 - 即時生效帶來的限制
兩種情況不適用,僅支持修復方法,其他情況補丁小,修復快。
1.引起了原有類中結構變化的修改
2.修復的非靜態方法被反射調用
2.2你所不知道的Java
內部類編譯
- 靜態內部類/非靜態內部類區別
內部類會被編譯器生成同外部類一樣的頂級類。只不過非靜態內部類會持有外部類的引用。這也是Android性能優化建議Handler使用靜態內部類,防止外部類Activity不能被回收導致造成OOM。 - 內部類和外部類互相訪問
內部類和外部類互相訪問private方法和字段時,會自動在對應類為對方生成public的access&**方法。 - 熱部署解決方案
外部類如果有內部類把所有的field/method的private訪問權限改成proteced或者public
內部類將所有的field/method的private訪問權限改成proteced或者public
匿名內部類編譯
- 匿名內部類命名規則
外部類&numble
number即編譯器根據匿名內部類出現在外部類中的順序,依次累加。 - 熱部署解決方案
新增/減少匿名內部類對熱部署是無解的,因為補丁修復工具拿到的是class文件,無法區別DexFileDemo&1和DexFileDemo&2,會導致類的順序亂套。如果匿名內部類插入到末尾是允許。
有趣的域編譯
- 靜態field,非靜態field編譯
熱部署不支持field/method增加和刪除和<clinit>方法的修改
靜態field的初始化和靜態代碼塊會被編譯在編譯器合成的方法<clinit>中
非靜態字段的初始化會被編譯在編譯器生成的<init>無參構造函數中 - 靜態field,靜態代碼塊
<clinit>方法會在類加載階段的類初始化時調用,<clinit>中靜態field和靜態代碼塊的出現順序就是二者在源碼中出現的順序。因為類已經加載過了,所以就算修復了<clinit>方法也不會生效了。
dvmResolveClass->dvmLinkClass->dvmInitClass,然后執行clinit方法
以下情況會去加載一個類
1.new 一個類的對象時new instance
2.調用類的靜態方法(invoke static)
3.獲取類的靜態域的值(sget) - 非靜態field,非靜態代碼塊
類的構造函數會被編譯器翻譯成<init>方法,會先進行非靜態field和非靜態代碼塊的初始化。它們出現的順序也是和在源碼中出現的順序一樣。
執行new instance指令時,如果類沒有加載過,就嘗試加載類。然后對對象內存分配,再然后執行invoke direct指令調用類的init構造函數進行初始化 - 熱部署解決方案
不支持對靜態字段和靜態代碼塊的修改,會導致熱部署失敗,只能冷啟動生效。支持非靜態字段和非靜態代碼塊修改,熱部署只是將init構造函數作為普通的方法變更。
final static 域編譯
- final static 域編譯規則
final static引用類型初始化仍在<clinit>中
final static基本類型和String類型,類加載初始化dvminitClass在執行clinit方法之前,先執行initSFields,這個方法為static域賦予默認值。引用類型默認NULL,final static修飾的基本類型和String類型會在這里初始化賦值。 - final static 域優化原理
final static基本類型執行const/4指令,操作數在dex中的位置(encoded_array_item)就是在opcode后一個字節。
final static String類型執行const-string指令,本質同上只不過拿到的是字符串常量在dex文件結構中字符串常量區的索引id。dex文件有一塊區域存儲所有的字符串常量會被完整的加載到虛擬機內存中-字符串常量區。
final static引用類型執行sget指令,首先調用dvmDexGetResolveField看這個域是否之前解析過,沒有的話調用dvmDexResolveField嘗試解析域,如果這個靜態域所在的類沒有解析過,嘗試調用dvmResolveClass,拿到這個sField,然后通過dvmDexGetResolveField(sField)獲取這個靜態值。 - 熱部署解決方案
final static基本類型/string類型最終引用的類型會被熱部署替換掉。
final static引用類型因為會被翻譯到clinit方法中,熱部署失敗。
有趣的方法編譯
- 應用混淆方法編譯
如果項目應用了混淆,會導致方法內聯和裁剪最終導致Method新增/減少 - 方法內聯
以下幾種情況下回發生方法內聯(該方法會被刪除)
1.方法沒有被任何地方引用
2.方法僅在一處被引用,調用方法的地方會被方法的實現替換掉
3.方法太簡單,僅僅一行語句。調用的語句也會被其實現所替換掉
如果補丁修復的方法突然調用了原先只有一處被調用的方法,那么原先被內聯掉的方法會新增出來,導致熱修復失敗。 - 方法裁剪(參數會被刪除)
方法的參數沒有被引用過,該方法會被裁剪,然后再進行混淆。如果補丁方法再次調用這個參數就會導致新增方法,那么只能走冷啟動方案。
可以采用走包裝類型Boolean判斷,簡單調用該參數,保持對該參數的引用就不會被裁剪了 - 熱部署解決方案
只要混淆配置文件中-dontoptmize就不會做方法內聯和裁剪了。所以不建議混淆時優化代碼。
而且因為Android執行的是優化后的dex文件,所以混淆中預校驗在class文件中的優勢就不存在了。
switch case語句編譯
- switch case語句編譯規則
編譯器會根據switch case的值是否連續分別生成不同的指令,packed-switch和sparse-switch指令。如果packed有值不連續就用pswitch_0補齊 return-void。 - 熱部署解決方案
在sophix進行資源補丁包時,需要對引用的資源進行替換,如果swith case語句恰好被編譯成packed-switch指令則可能會漏掉。解決方法是修改打補丁包時的smail反編譯流程,碰到packed-switch指令強轉為sparse-switch指令,:pswitch_N等標簽指令也需要被替換成:sswitch_N指令,然后做資源Id替換,編程smail為dex
泛型編譯
- 為什么需要泛型
Java泛型完全有編譯器實現,由編譯器執行類型檢查和類型推斷,生成非泛型字節碼,稱之為擦除。
沒有泛型之前想要實現類泛型,利用所有類的父類時Object進行強轉,這完全依賴程序員的自主性,很容易出現ClassCastException。泛型的出現解決了類型檢查和類型推斷的問題。 - 泛型類型擦除
Java字節碼中不包含泛型類型信息,想要區別類型定義可以限定泛型類型 <T extends Number> - 類型擦除與多態的沖突和解決
父類是泛型類有setNumber(T value),子類想override setNumber(Number value)。然而實際父類的方法實際是setNumber(Object value),子類想重寫卻變成了重載,這就出現了類型擦除和多態之間的沖突。然而編譯器自動幫我們合成了Bridge方法實現了重載,在子類中生成了相同簽名bridge方法,內部實際調用子類的重寫方法。 - 泛型類型轉換
編譯器如果發現變量聲明加上了泛型信息,編譯器自動加上了check-cast的強制轉換,因為編譯器會為泛型做類型檢查,所以自動的強制轉換不會出現ClassCastException。 - 熱部署解決方案
如果父類補丁變成了增加了泛型則會增加Bridge方法,造成熱部署失敗。
將方法從void get(B t) 變成<B extends Number> void get(B t)方法邏輯不會發生變化,但是方法的簽名會發生變化,這種情況熱修復沒有意義,需要避免這種情況的發生。
Lambda表達式編譯
- Lambda表達式編譯規則
Lamda表達式具有函數式編程的特點,是Java中最接近閉包的概念。函數式接口:一個接口具有唯一一個抽象方法
Java中的Runable和Comparator都是典型的函數式接口
Lamada表達式和匿名內部類的區別:
1.this關鍵字指包圍Lamada表達式的類而不是指向匿名內部類自己
2.編譯方式,Java編譯器將Lamda表達式編譯成類的私有方法,使用了Java7的invokedynamic動態綁定這個私有方法。而匿名內部類則是生成外部類&number的新類
編譯器都會在類下生成lamda$main$**{ * }私有靜態方法,這個方法實現了lamda表達式的邏輯,引用的變量都會變成方法的參數。
在HostSpot VM下解釋class文件的lamda表達式:
invokeDynamic指令調用java/lang/invoke/LamdaMetafactory的metafactory這個靜態方法。這個方法會在運行時生成實現函數式接口的具體類,這個具體類會調用那個靜態私有方法。
在Android虛擬機下解釋dex文件中的lamda表達式:則是在優化成dex文件的時候就生成了這個具體類。 - 熱部署解決方案
新增lamada表達式會導致外部類新增一個輔助方法。修改的lamda表達式邏輯引用了外部變量,會導致輔助類持有了外部對象,會新增這個外部對象的變量。也是會導致熱修復失敗。
訪問權限檢查對熱替換的影響
- 類加載階段父類/實現接口訪問檢查
一個類的加載階段包括resolve->link->init三個階段,父類/實現接口 權限檢查在link階段,dvmLinkClass中依次對父類/實現接口進行dvmCheckClassAcess權限檢查,如果父類/實現接口是非public然后進行檢查當前類和它是否是相同的ClassLoader。熱修復補丁類是新classLoader加載的,所在會報父類不允許訪問的錯誤。 - 類校驗階段訪問權限檢查
如果訪問public類和方法在類加載階段會通過,但是在運行時會爆出crash異常。
補丁在單個dex文件中,加載dex肯定要進行dexopt,再dexopt過程中會dvmVerifyClass校驗dex每個類。在校驗過程中會檢查補丁類所引用類的訪問權限(提前dvmResolveClass被調用類)。還會校驗調用方法的訪問權限,public修飾直接返回。protected的話,先檢查當前類和被調用方法所屬類是否是父子類關系,不是的話會調用dvmIsSmaePackage,這里會判斷是否是相同的classLoader。...
<clinit>方法
該方法無法被熱修復,只能走冷啟動。
2.3冷啟動類加載原理
類啟動方案實現概述
QQ空間超級補丁采用的插樁方式,入侵打包流程,單獨放一個幫助類在獨立的dex中讓其他類調用,阻止類在dexopt時被打傷CLASS_ISPREVERIFIED標記。加載補丁dex得到dexFile對象作為參數構建一個Element對象插入到dexElement數組最前面。
Tinker提供差量包,整體替換dex的方案。將patch.dex與應用的class.dex合并生成一個完整的dex,加載完整的dex得到dexFile對象為參數構建一個Element對象替換dexElements數組。
官方multiDex
沒有補丁查詢更新,下載補丁待下次啟動時生效。
插樁實現前因后果
插樁方案是在dalvik和art下通用的冷啟動方案。
將dex加載到本地內存時,如果不存在odex文件,首先會進行dexopt。dexopt會調用verify/optimize操作。Apk第一次安裝時,會對dex執行dexopt,如果只存在一個dex文件則dvmVerifyClass(class)會打上CLASS_IS_PREVERIFIED標記。然后執行dvmOptimizeClass(class)打上CLASS_IS_OPTIMIZED標記。
dvmVerifyClass:防止類被篡改校驗類的合法性。我們主要關心會校驗類的所有方法直接引用類和當前類是否屬于同一個dex
-
dvmOptimizeClass:類優化,將部分指令優化成虛擬機內部指令,如invoke-=>invoke--quick。quick指令會直接從vtable中獲取方法地址,vtable是記錄了類的所有方法。加快執行速度。
補丁類存在獨立的dex中,類A訪問補丁類的方法。嘗試解析dvmResolveClass補丁類時,會判斷referrer類打上標記然后校驗referrer類和當前類是否是同一個dex,拋出dvmIllegeThrowAccessException。 為了解決問題,通常的做法是入侵打包流程,利用class字節碼修改技術在每個類的構造函數中引用獨立dex中的幫助類。防止Apk安裝時被打上CLASS_IS_PREVERFIED標記,因此解決了這個異常問題。 類在運行時初始化時沒打上標記還會在執行一次verifyClass/optimizeClass。dvmInitClass完成父類初始化,當前類初始化以及static字段初始化賦值。dvmVerifyClass非常重,對類方法的所有指令進行校驗,雖然單個類影響不大,但應用加載大量類時,會導致非常耗性能。
插樁導致類加載性能影響
在應用啟動場景下會加載大量的類,啟動時容易出現白屏。
避免插樁的QFix方案
在native層提前調用dvmResolveClass,是的在dvmResolve中調用dvmDexGetResolve不為null,也避免了校驗一致性的問題。
這個方案要求傳遞的在多dex情況下,referrer類必須跟patch類是同一個dex。fromUnverifiedConstant必須為true。referrer必須提前加載。
這方案還要一些問題,在dexopt之后繞過,但是dexopt會改變很多原先的邏輯,許多odex層面的優化會寫死字段和訪問方法的偏移。這會造成很嚴重的BUG。
Art下冷啟動實現
除了通用的插樁方案,針對Art下的冷啟動方案實現如下。
為了使熱部署和冷啟動共用一個補丁,熱部署模式下的補丁能夠降級直接走冷啟動,所以不需要dex merge。而Thinker為了解決Art下odex地址偏移寫死的問題,合并成一個全新完整的dex得到dexElement整體替換舊的dexElements。
- Dalvik-dexload:在加載原dex文件和jar壓縮文件中只會把classes.dex文件加載到內存,然后會生成odex文件。
- Art-dexload:打開壓縮文件加載多個dex文件,首先加載的是primaryDex(classes.dex),后續加載其他dex文件。方法調用鏈:DexFile_openDexFileNative->openDexFileFromOat(從oat文件中加載)->LoadDexFiles(從壓縮文件中加載)
補丁類的dex只需要改成classes.dex,其他原dex文件依次更名為classes(2,3...).dex,一起打包一個壓縮文件。先加載classes.dex的補丁類就不會再加載其他dex中的補丁類了。
不得不說的其他點
DexFile.loadDex會將一個dex加載到內存,在加載到內存之前,如果dex不存在對應odex,在Dalvik下會執行dexopt,Art下會執行dexoat。
如果dex足夠大,會導致dexopt/dexoat很耗時。針對Art啟動耗時的方案(感覺這里有誤,art下優化文件應該是oat而不是odex,loadDex方案贊同),sophix方案在art下dexopt處理的是patch.dex和原理的dex的壓縮包,所以dexoat非常耗時。所以如果優化后的odex沒有生成就不能夠在應用啟動的時候loadDex,只能開子線程loadDex。將loadDex看做事務,一旦打斷就刪除odex,生成odex之后,如果應用重啟發現生成了odex文件就loadDex,然后就反射替換dexElements數組。如果不存在就重啟另外一個子線程loadDex,重啟之后再生效。
還會對補丁進行簽名校驗和對odex進行md5校驗,不對就重新生成,防止文件被篡改。
完整方案的考慮
在補丁加載類之前的類是無法被修復的(Application類)
在Dalvik采用阿里自己研發的全量dex方案
在art下僅僅是將差量補丁包作為primaryDex(classes.dex)加載
2.4多態對冷啟動加載的影響
重新認識多態
多態用的是動態綁定技術,在運行期判斷引用對象的實際類型,根據實際類型調用方法。動態指非靜態方法和非私有方法即public/proteced/default,字段沒有多態。
那么Android中是如何實現多態呢?
Android對動態綁定技術做了優化,在類初始化時一次性動態綁定好,然后在運行時調用該類的virtual方法直接讀取方法引用,調用就可以了。
具體實現是:
初始化時
1.為類創建一個vtable表,vtable實際是記錄類的所有virtual方法的方法指針。
2.確定vtable數組的最大大小為父類vtable的大小+子類virutal方法數
3.初始化vtable內容,將父類的vtable復制到子類的vtable中
4.遍歷子類的vitual方法,判斷方法的原型是否和vtable中一樣,相同則重寫該方法實際引用
5.如果方法原型不同則將子類的virtual方法添加到vtable中的末尾
調用時
1.類方法invoke virtual指令在Android dex第一次加載時執行dexopt的dvmOptimizeClass中會被優化成invoke-virtual-quick虛擬機內部指令
2.確定當前變量的實際類型,quick指令會直接從類的vtable中獲取方法指針進行執行,加f快執行速度
sget/invoke static指令簡述
從當前變量的引用類型找,而不是實際類型。找不到就遞歸從父類中查找
冷啟動方案限制
QFix方法在Davik下,dex執行dexopt的dvmOptimize時將invoke-virtual指令優化成了invoke-virtual-quick指令。指令后面跟的立即數就是方法在vtable中索引值(這里有個問題,我們從上面知道類在初始化時才會確定vtable中各子類的索引值。而dexopt不沒有初始化類,猜測是提前使用了動態綁定的分析方法確定了vtable的索引值)。
patch類新增類virtual方法,如果patch類是父類,則該父類加載之后就會使得子類中繼承父類vtable中索引值混亂了。比如子類原本重寫了父類的A方法,而修復之后的父類的B方法在A方法之前,實際就是A方法重寫了新增的B方法,在想要調用子類對象的中父類新增的B方法時卻調用被覆蓋的A方法。
終極方案
QFix方法不可行,而Tinker將多個dex合并成完整的dex方案可以解決這個問題。
google開源了dexmerge方法,將補丁dex和原dex合并成一個完整的dex,但是多個dex合并會有65536的問題,而且還會非常消耗內存,內存不足會導致合并失敗。2.5會說明完美方案。
2.5 Dalvik下完整DEX方案的新探索
冷啟動類加載修復
QQ空間
- dex插入方案,將補丁dex插入到classLoader索引路徑最前面
- 通過插樁方式繞過Dalvik虛擬機下類的pre-verify問題
QFix
- 同是dex插入方案
- 通過在Jni層提前resolve所有補丁類繞過pre-verify檢查
Tinker:
- 全量合成新dex,消除重復class重復帶來的沖突
- 所有類加載都在一個dex完成,沒有pre-verify問題
一種新的全量dex方案
在基線包里去除掉補丁包里的class后,然后將其他的dex都load進來。基線包可以找到補丁中新類,補丁包中新類也可以找到基線包中不變的類。這樣基線包中不變的class仍然可以按照舊的邏輯odex,最大程度保證了dexopt的效果。
在dex文件中類的入口在DexHeader中表現為class_defs。遍歷pHeader->classDefsOff偏移值獲取DexClassDef,如果發現這個類名存在補丁中就從pClassDefs數組中移除,重新排列,修改classDefsSize。這樣修改不去移除類的定義等信息,雖然會殘留類等信息,但是會提高dex的處理速度。
對于Application的處理
Sophix在Applicatoin類加載補丁之后,清除Applicatoin類的pre_verified標記,使得跨dex加載類不會報異常。不侵入編譯流程,不進行反射,在運行期自動做好。
Tinker要求開發者聲明TinkerApplication,將真正的Application作為參數傳入TinkerApplication,應用啟動時啟動Tinker自己的熱修復邏輯,在聲明周期回調時通過反射執行原來的Application邏輯。
Amigo在編譯過程中通過gradle插件將Application替換成它的Application,修復完成之后調用舊Application的attach(context),最后調用舊Application中的oncarete()調用原來的邏輯。
清除pre_verified邏輯:在jni層清除標記 clazzObj->accessFlags=~CLASS_PREVERIFIED.
dvmOptResolveClass問題與對策
清除標記遇到問題。多dex應用時,如果在Application類沒有被打上pre_verified標記,那么虛擬機在初始化類時會掃描其所有用到的類進行dvmOptResolveClass操作,這個方法對類進行初始化加載的是原dex的類,那么這些類就會被打上pre_verified標記。補丁加載之后,只對Application類進行了清除標記操作。那些打上標記的類調用補丁類的話還是會出現pre_verified問題。
繞過dvmOptResovleClass操作,僅僅讓Application類被打上標記的解決方案
- 讓Application用到的非系統類和Application在同一個dex中,保證打上pre_verified標記,避免進入dvmOptResolveClass操作。(Android官方的multi-dex就是將Application用到的類都打包到主dex中)
- 讓Application類除了熱修復代碼之外,其他代碼剝離開放到一個單獨的類。Application通過反射調用其他類,是的Application徹底與其他類獨立開,保證被打上pre_verified標記。
3資源熱修復技術
3.1普遍的實現方式
InstantRun實現分兩步
- 構造新的AssetManager,反射調用addAssetPath添加新的完整的資源包路徑到AssetManager
- 找到之前所有引用到原有的AssetManager的地方,通過反射將引用處替換為新的AssetManager
一個Android進程只有一個AssetManager, AssetManager->mResources->mResTable,從資源包路徑解析出resource.arsc(記錄了所有資源id分配情況和資源中所有字符串,以二進制信息存儲),將其相關信息存儲到mResTable.mPackageGroups(解析過的所有資源包的集合)
3.2資源文件的格式
resource.arsc由一個個ResChunk拼接起來,ResChunk頭部都是一個ResChunk_header結構 (指示了大小和類型)
每個Android資源信息都是一個32為的編號0xPPTTEEEE,PP為packageId,TT為typeid,EEEE為entryid.
- packageid對應ResTable_package結構體的id
- typeid對應ResTable_typeSpec結構體的id,typeid對應的具體類型需要到package chrunk里的String Pool解析到。
- 每個entry表示一個資源項,資源項是按照排列的先后順序被自動標記編號。
3.3運行時資源的解析
應用運行第一行代碼時,系統已經構建好AssetManager,包含系統包資源id為0x01,安裝包資源0x7f,aapt打包應用資源packageid都是為0x7f.
如果在這個原assetManager基礎上添加補丁包資源。
Android KK下。addAssetPath 只將補丁包路徑添加到mAssetPath中,而在此之前資源包已經被解析過了,所以補丁資源包不會生效。
在Android L上。因為packageid相同,會添加同一個packageGroup下。補丁資源會添加到Type資源集合后面。獲取某個type資源的時候會從前往后遍歷,新補丁資源則仍不會生效。
所以InstantRun方案,需要一個全新的AssetManager,添加一個完整的資源包,替換掉原來的AssetManager.
3.4另辟蹊徑的資源修復方案
采用差量資源包,補丁足夠小,不入侵打包流程。
構建差量的0x66的補丁資源包,只包含改變之后的資源項。然后直接在原有的AssetManager中addAssetPath這個資源包。并修正補丁包中引用的資源id,舊資源id發生變化的id。
3.5更優雅的替換AssetManager
在Android L以后的版本直接在原來的AssetManager添加資源包就行了。
在Android kk下,調用native層銷毀AssetManager資源,將java層的AssetManager指向native層的AssetManager設置為空,native層的AssetManager的析構函數會釋放之前加載的所有資源。java層的AssetManager成為空殼之后就可以重新初始化了。初始化之后我們再AddAssetPath就會生效了。
sophix的資源修復,需要代碼修復支持,也只在新代碼中生效。
4.SO庫熱修復技術
4.1SO庫加載原理
so庫加載句柄,從so庫中加載方法映射到內存中的hash表中,此時方法代碼應該被加載到內存中。
4.2SO庫熱部署實時生效可行性分析
動態注冊:解析映射native方法,第一次走原so包,第二次走補丁包
靜態注冊:加載補丁包之后,解注冊靜態的jni native方法,重新映射
4.3SO庫冷部署重啟生效實現方案
art:在解析nativeLibaryDirectorys中插在最前面 補丁包
dalvik:重命名補丁包,同樣方法