很早之前就想深入的研究和學(xué)習(xí)一下熱修復(fù),由于時(shí)間的原因一直拖著,現(xiàn)在才執(zhí)筆弄起來(lái)。
Android而更新系列:
Android熱更新一:JAVA的類(lèi)加載機(jī)制
Android熱更新二:理解Java反射
Android熱更新三:Android類(lèi)加載機(jī)制
Android熱更新四:熱修復(fù)機(jī)制
Android熱更新五:四大熱修復(fù)方案分析
Android熱更新六:Qzone熱更新原理
Android熱更新七:Tinker熱更新原理
Android熱更新八:AndFix熱更新原理
Android熱更新九:Robust熱更新原理
Android熱更新十:自己寫(xiě)一個(gè)Android熱修復(fù)
??超級(jí)補(bǔ)丁技術(shù)基于DEX分包方案,使用了多DEX加載的原理,大致的過(guò)程就是:把BUG方法修復(fù)以后,放到一個(gè)單獨(dú)的DEX里,插入到dexElements數(shù)組的最前面,讓虛擬機(jī)去加載修復(fù)完后的方法。

??當(dāng)patch.dex中包含Test.class時(shí)就會(huì)優(yōu)先加載,在后續(xù)的DEX中遇到Test.class的話(huà)就會(huì)直接返回而不去加載,這樣就達(dá)到了修復(fù)的目的。
??但是有一個(gè)問(wèn)題是,當(dāng)兩個(gè)調(diào)用關(guān)系的類(lèi)不在同一個(gè)DEX時(shí),就會(huì)產(chǎn)生異常報(bào)錯(cuò)。我們知道,在APK安裝時(shí),虛擬機(jī)需要將classes.dex優(yōu)化成odex文件,然后才會(huì)執(zhí)行。在這個(gè)過(guò)程中,會(huì)進(jìn)行類(lèi)的verify操作,如果調(diào)用關(guān)系的類(lèi)都在同一個(gè)DEX中的話(huà)就會(huì)被打上CLASS_ISPREVERIFIED的標(biāo)志,然后才會(huì)寫(xiě)入odex文件。
??所以,為了可以正常地進(jìn)行打補(bǔ)丁修復(fù),必須避免類(lèi)被打上CLASS_ISPREVERIFIED標(biāo)志,具體的做法就是單獨(dú)放一個(gè)類(lèi)在另外DEX中,讓其他類(lèi)調(diào)用。
??我們來(lái)逆向手機(jī)QQ空間APK看一下具體的實(shí)現(xiàn):
??先進(jìn)入程序入口QZoneRealApplication,在attachBaseContext中進(jìn)行了兩步操作:修復(fù)CLASS_ISPREVERIFIED標(biāo)志導(dǎo)致的unexpected DEX problem異常、加載修復(fù)的DEX。

1. 修復(fù)Unexpected DEX Problem異常
??先看代碼,

??可以看到,這里是要加載一個(gè)libs目錄下的dalvikhack.jar。在項(xiàng)目的assets/libs找到該文件,解壓得到’classes.dex’文件,逆向打開(kāi)該DEX文件,

??通過(guò)不同的DEX加載進(jìn)來(lái),然后在每一個(gè)類(lèi)的構(gòu)造方法中引用其他DEX中的唯一類(lèi)AnitLazyLoad,避免類(lèi)被打上CLASS_ISPREVERIFIED標(biāo)志。

??在無(wú)修復(fù)的情況下,將DO_VERIFY_CLASSES設(shè)置為false,以提高性能。只有在需要修復(fù)的時(shí)候,才設(shè)置為true。

??至于如何加載進(jìn)來(lái),與下面第二個(gè)步驟基本相同。
2. 加載修復(fù)的DEX
??從loadPatchDex()方法進(jìn)入,經(jīng)過(guò)幾次跳轉(zhuǎn),到達(dá)核心的代碼段,SystemClassLoaderInjector.c()。由于進(jìn)行了混淆和多次方法的跳轉(zhuǎn),于是將核心代碼段做了如下整理:

修復(fù)的步驟為:
- 可以看出是通過(guò)獲取到當(dāng)前應(yīng)用的Classloader,即為BaseDexClassloader
- 通過(guò)反射獲取到他的DexPathList屬性對(duì)象pathList
- 通過(guò)反射調(diào)用pathList的dexElements方法把patch.dex轉(zhuǎn)化為Element[]
- 兩個(gè)Element[]進(jìn)行合并,把patch.dex放到最前面去
- 加載Element[],達(dá)到修復(fù)目的
整體的流程圖如下:

??從流程圖來(lái)看,可以很明顯的找到這種方式的特點(diǎn):
優(yōu)勢(shì):
沒(méi)有合成整包(和微信Tinker比起來(lái)),產(chǎn)物比較小,比較靈活
可以實(shí)現(xiàn)類(lèi)替換,兼容性高。(某些三星手機(jī)不起作用)
不足:
- 不支持即時(shí)生效,必須通過(guò)重啟才能生效。
- 為了實(shí)現(xiàn)修復(fù)這個(gè)過(guò)程,必須在應(yīng)用中加入兩個(gè)dex!dalvikhack.dex中只有一個(gè)類(lèi),對(duì)性能影響不大,但是對(duì)于patch.dex來(lái)說(shuō),修復(fù)的類(lèi)到了一定數(shù)量,就需要花不少的時(shí)間加載。對(duì)手淘這種航母級(jí)應(yīng)用來(lái)說(shuō),啟動(dòng)耗時(shí)增加2s以上是不能夠接受的事。
- 在ART模式下,如果類(lèi)修改了結(jié)構(gòu),就會(huì)出現(xiàn)內(nèi)存錯(cuò)亂的問(wèn)題。為了解決這個(gè)問(wèn)題,就必須把所有相關(guān)的調(diào)用類(lèi)、父類(lèi)子類(lèi)等等全部加載到patch.dex中,導(dǎo)致補(bǔ)丁包異常的大,進(jìn)一步增加應(yīng)用啟動(dòng)加載的時(shí)候,耗時(shí)更加嚴(yán)重。