目錄
1什么是熱修復(fù)
2.為什么需要熱修復(fù)
3.目前存在的解決方案
4.各自?xún)?yōu)缺點(diǎn)
5.如何應(yīng)用和取舍
1.什么是熱修復(fù)
傳統(tǒng)發(fā)版流程民逼。
在這個(gè)流程當(dāng)中發(fā)版重新上線(xiàn)需要用戶(hù)重新下載覆蓋安裝無(wú)法做到全面替換,而且一次安裝包較大用戶(hù)安裝的成本較高。
替代方案之一:Hybird.
缺點(diǎn)是采用H5以后性能與原生相比相差甚遠(yuǎn)。
更優(yōu)的解決方案:熱修復(fù).
熱修復(fù)的出現(xiàn)將傳統(tǒng)發(fā)版流程修改成如下流程:
熱修復(fù)流程在修復(fù)bug以后采用無(wú)感知方式直接推送給所有用戶(hù)無(wú)需安裝即可修復(fù),有點(diǎn)是覆蓋面廣修復(fù)成功率高益眉,且比下載一個(gè)apk安裝成本要低。
2.為什么需要熱修復(fù)
上面實(shí)際上已經(jīng)解答了這個(gè)問(wèn)題。
3.目前存在的解決方案:
阿里:andFix HotFix sophix
騰訊:Tinker? qq空間熱補(bǔ)丁方案?
美團(tuán):Robust
根據(jù)不同技術(shù)方案實(shí)現(xiàn)重新對(duì)上述方案進(jìn)行分類(lèi):
底層替換方案:AndFix
multidex方案:Tinker qq空間熱補(bǔ)丁方案郭脂,nuwa等
基于instant run方案:robust.
3.1底層替換方案:
AndFix主要是在native層使用指針替換的方式替換bug方法年碘,通過(guò)自定義注解找到需要修復(fù)的類(lèi)和方法。首先類(lèi)加載其方法都放在方法區(qū)展鸡,在native層中有相應(yīng)的結(jié)構(gòu)體描述對(duì)應(yīng)的方法和執(zhí)行邏輯屿衅,一旦某個(gè)方法出現(xiàn)bug,可以新建一個(gè)類(lèi)然后把修復(fù)后的方法放進(jìn)來(lái)莹弊,把原來(lái)的那個(gè)類(lèi)的方法的指針指向新的類(lèi)方法就完成了bug修復(fù)涤久。
Sophix:是在A(yíng)ndFix進(jìn)一步優(yōu)化產(chǎn)生的,由于不同廠(chǎng)商的底層結(jié)構(gòu)體不同忍弛,按照AndFix的方式很難兼容所有平臺(tái)响迂。而Sophix很巧妙的解決了這個(gè)問(wèn)題,結(jié)構(gòu)體的替換例如修改指針细疚,改訪(fǎng)問(wèn)權(quán)限其前提是Android手機(jī)的結(jié)構(gòu)體與AndFix的結(jié)構(gòu)體要一致才能才能完好的進(jìn)行替換蔗彤,所以Sophix另辟蹊徑采用整體替換的方式,
同包名訪(fǎng)問(wèn)權(quán)限問(wèn)題采用classloader進(jìn)行替換將新的替換為老的即可疯兼。
QQ超級(jí)補(bǔ)丁方案:
基于A(yíng)ndroid dex分包機(jī)制完成
QQ超級(jí)補(bǔ)丁的原理基于A(yíng)ndroid dex加載原理幕与,新修復(fù)的類(lèi)放在patch.dex中插樁到dexElements最前面,就算是完成了bug熱修復(fù)镇防,虛擬機(jī)優(yōu)先加載patch.dex中的qzone.class啦鸣,為解決不在不同一個(gè)dex的兩個(gè)調(diào)用類(lèi)關(guān)系。也就是防止類(lèi)被打上CLASS_ISPREVERIFIED標(biāo)志来氧,設(shè)計(jì)一個(gè)AntilazyLoad類(lèi)诫给,然后在所有類(lèi)的構(gòu)造方法中加入
if (ClassVerifier.PREVENT_VERIFY) {?
????System.out.println(AntilazyLoad.class);
?}
同時(shí)AntilazyLoad類(lèi)會(huì)單獨(dú)打包成hack.dex,這樣所有的類(lèi)都會(huì)引用不同的dex
修復(fù)的步驟為:
1.獲取當(dāng)前用的Classloader啦扬,即為BaseDexClassloader中狂,目的會(huì)為了獲取其pathlist
2.分別獲取程序和補(bǔ)丁的pathList得到兩者的dexElements然后進(jìn)行合并,將補(bǔ)丁放到最前面
Tinker:
dalvik由于插樁導(dǎo)致耗時(shí)扑毡,性能損耗大胃榕,在art狀態(tài)下修改方法和filed會(huì)導(dǎo)致內(nèi)存指針異常,為了解決這個(gè)問(wèn)題我們需要將修改了變量瞄摊、方法以及接口的類(lèi)的父類(lèi)以及調(diào)用這個(gè)類(lèi)的所有類(lèi)都加入到補(bǔ)丁包中勋又。這可能會(huì)帶來(lái)補(bǔ)丁包大小的急劇增加。
全量替換新dex换帜,對(duì)比新舊dex楔壤,在運(yùn)行時(shí)將path.dex與舊dex還原成新的dex,DexDiff的細(xì)粒度是dex中每一項(xiàng)
缺點(diǎn):
占用Rom體積惯驼;這邊大約是你修改Dex數(shù)量的1.5倍(dexopt與dex壓縮成jar)的大小蹲嚣。
一個(gè)額外的合成過(guò)程递瑰;雖然我們單獨(dú)放在一個(gè)進(jìn)程上處理,但是合成時(shí)間的長(zhǎng)短與內(nèi)存消耗也會(huì)影響最終的成功率隙畜。
難點(diǎn):
Dex格式復(fù)雜抖部;Dex大致分為像StringID,TypeID這些Index區(qū)域以及使用Offset的Data區(qū)域议惰。它們有大量的互相引用慎颗,一個(gè)小小的改變可能導(dǎo)致大量的Index與Offset變化;
dex2opt與dex2oat校驗(yàn)换淆;在這兩個(gè)過(guò)程系統(tǒng)會(huì)做例如四字節(jié)對(duì)齊哗总,部分元素排序等校驗(yàn),例如StringID按照內(nèi)容的Unicode排序倍试,TypeID按照StringID排序...
低內(nèi)存讯屈,快速;這要求我們對(duì)Dex每一塊做到一次讀寫(xiě)县习,無(wú)法像baksmali與dexmerge那樣完全結(jié)構(gòu)化涮母。
robust
遇到的難點(diǎn):混淆以及super函數(shù)
缺點(diǎn):太過(guò)復(fù)雜。無(wú)法做到資源.so庫(kù)替換和AndFix類(lèi)似也是方法替換
1.DexClassLoader和PathClassLoader.
DexClasLoader加載jar躁愿,dex以及apk的classes.dex文件叛本,可以執(zhí)行非安裝程序代碼
PathClassLoader是Android使用這個(gè)類(lèi)作為系統(tǒng)和應(yīng)用類(lèi)的加載器。
#DexPathList
public Class findClass(String name) {
????for(Element element : dexElements) {
????????DexFile dex = element.dexFile;
????????if(dex !=null) {
? ? ? ?????Class clazz = dex.loadClassBinaryName(name, definingContext);
????????????if(clazz !=null) {
????????????????return clazz;
????????????}
????????}
????}
????return null;
}
將dex轉(zhuǎn)化成odex的過(guò)程中彤钟,會(huì)在DexVerify.cpp進(jìn)行校驗(yàn)来候,驗(yàn)證如果直接引用到的類(lèi)和clazz是否在同一個(gè)dex,如果是逸雹,則會(huì)打上CLASS_ISPREVERIFIED標(biāo)志营搅,需要阻止打上這個(gè)標(biāo)記。
instant run原理
ART所使用的AOT(Ahead-Of-Time)編譯梆砸,在應(yīng)用首次安裝時(shí)转质,字節(jié)碼預(yù)編譯成機(jī)器碼存儲(chǔ)在本地,也就是說(shuō)在程序運(yùn)行前編譯帖世。而Dalvik是典型的JIT(Just_In_Time)休蟹,此模式下,應(yīng)用每次運(yùn)行的時(shí)候日矫,字節(jié)碼都需要即時(shí)編譯器轉(zhuǎn)換為機(jī)器碼再執(zhí)行赂弓,也就是在程序運(yùn)行時(shí)編譯。因此在A(yíng)pp運(yùn)行時(shí)搬男,ART模式相對(duì)于Dalvik省去了解釋字節(jié)碼的過(guò)程拣展,占用內(nèi)存也相應(yīng)減少,進(jìn)而提高App缔逛。
? ?public int getIndex() {
? ? return 105;
}
public static ChangeQuickRedirect changeQuickRedirect;
public long getIndex() {
if(changeQuickRedirect != null) {
//PatchProxy中封裝了獲取當(dāng)前className和methodName的邏輯,并在其內(nèi)部最終調(diào)用了changeQuickRedirect的對(duì)應(yīng)函數(shù)
if(PatchProxy.isSupport(new Object[0], this, changeQuickRedirect, false)) {
return ((Long)PatchProxy.accessDispatch(new Object[0], this, changeQuickRedirect, false)).longValue();
}
}
return 100L;
}
public class PatchesInfoImpl implements PatchesInfo { public ListgetPatchedClassesInfo() { ListpatchedClassesInfos = new ArrayList();
PatchedClassInfo patchedClass = new PatchedClassInfo("com.meituan.sample.d", StatePatch.class.getCanonicalName());
patchedClassesInfos.add(patchedClass);
return patchedClassesInfos;
}
}
public class StatePatch implements ChangeQuickRedirect {
@Override
public Object accessDispatch(String methodSignature, Object[] paramArrayOfObject) {
String[] signature = methodSignature.split(":");
if (TextUtils.equals(signature[1], "a")) {//long getIndex() -> a
return 106;
}
return null;
}
@Override
public boolean isSupport(String methodSignature, Object[] paramArrayOfObject) {
String[] signature = methodSignature.split(":");
if (TextUtils.equals(signature[1], "a")) {//long getIndex() -> a
return true;
}
return false;
}
}
直接用DexClassLoader加載修復(fù)包,然后用loadClass方法加載修復(fù)類(lèi)褐奴,new出新對(duì)象按脚,在用反射把這新的修復(fù)對(duì)象設(shè)置到指定類(lèi)的changeQuickRedirect靜態(tài)變量中即可。
第一次編譯apk:
1.把Instant-Run.jar和instant-Run-bootstrap.jar打包到主dex中
2.替換AndroidManifest.xml中的application配置
3.使用asm工具敦冬,在每個(gè)類(lèi)中添加$change辅搬,在每個(gè)方法前加邏輯
4.把源代碼編譯成dex,然后存放到壓縮包instant-run.zip中
app運(yùn)行期:
1.獲取更改后資源resource.ap_的路徑
2.設(shè)置ClassLoader脖旱。setupClassLoader:
使用IncrementalClassLoader加載apk的代碼堪遂,將原有的BootClassLoader → PathClassLoader改為BootClassLoader → IncrementalClassLoader → PathClassLoader繼承關(guān)系。
3.createRealApplication:
創(chuàng)建apk真實(shí)的application
4.monkeyPatchApplication
反射替換ActivityThread中的各種Application成員變量
5.monkeyPatchExistingResource
反射替換所有存在的AssetManager對(duì)象
6.調(diào)用realApplication的onCreate方法
7.啟動(dòng)Server萌庆,Socket接收patch列表
有代碼修改時(shí)
1.生成對(duì)應(yīng)的$override類(lèi)
2.生成AppPatchesLoaderImpl類(lèi)溶褪,記錄修改的類(lèi)列表
3.打包成patch,通過(guò)socket傳遞給app
4.app的server接收到patch之后践险,分別按照handleColdSwapPatch猿妈、handleHotSwapPatch、handleResourcePatch等待對(duì)patch進(jìn)行處理
5.restart使patch生效