
Android開發(fā)中,熱修復(fù)技術(shù)是不可缺少的,市面上也出現(xiàn)很多成熟的開源框架。但是對(duì)于很多Android開發(fā)者來(lái)說(shuō),理解熱修復(fù)還是有一定的難度的,也許很多開發(fā)者會(huì)使用框架,但是我覺(jué)得是遠(yuǎn)遠(yuǎn)不夠的,為了成長(zhǎng),其中的原理還是需要我們?nèi)ダ斫獾摹Wプ≈饕募夹g(shù),不管怎么變,技術(shù)相同,操作的方式不同,可能框架顯示的效果就不一樣。
我們?yōu)槭裁葱枰獰嵝迯?fù)?
當(dāng)我們發(fā)現(xiàn)已經(jīng)上線的app存在某一小部分bug時(shí)候(注意是一小部分bug,問(wèn)題太多了的話還是建議發(fā)新版本吧),需要進(jìn)行修復(fù),可以讓用戶下載補(bǔ)丁包進(jìn)行修復(fù)。
市場(chǎng)上已經(jīng)有很多的成熟的熱修復(fù)方案:
1.Tinker
2.QZone
3.AndFix
4.Robust
熱修復(fù)解決方案對(duì)比:

AndFix
在native動(dòng)態(tài)替換java層的方法,通過(guò)native層hook java層的代碼。
Robust
Robust插件對(duì)每個(gè)產(chǎn)品代碼的每個(gè)函數(shù)都在編譯打包階段自動(dòng)的插入了一段代碼,插入過(guò)程是對(duì)業(yè)務(wù)開發(fā)完全透明。(字節(jié)碼插樁技術(shù),在class字節(jié)碼中寫代碼)
Tinker
Tinker通過(guò)計(jì)算對(duì)比指定的Base Apk中的dex與修改后的Apk中的dex區(qū)別,補(bǔ)丁包中的內(nèi)容即為兩者差分的描述。運(yùn)行時(shí)將Base Apk中的dex與補(bǔ)丁包進(jìn)行合成,重啟后加載全新的合成后的dex文件。
QZone原理與Tinker相似
在寫一個(gè)簡(jiǎn)單的熱修復(fù)之前我們需要有一些知識(shí)儲(chǔ)備:反射,和熟悉類加載原理
ClassLoader(類加載器)原理分析:

代碼示例如下:

打?。?/b>

由代碼可以知道:
1.Pathclassloader 是加載程序的類的也就是我們自己寫的類或者第三方寫的類
2.BootClassLoader是加載framework層的代碼
源碼分析:
Pathclassloader是由ActivityThread創(chuàng)建的程序的ClassLoader,我們直接去看Pathclassloader
Pathclassloader代碼:注意(先記住)該構(gòu)造方法中parent是BootClassLoader

我們要找loadClass方法,發(fā)現(xiàn)沒(méi)有:就去找它的父類:

也沒(méi)有l(wèi)oadClass方法,再去找父類 :ClassLoader

發(fā)現(xiàn)是有的,看它調(diào)用過(guò)程:

會(huì)調(diào)用兩個(gè)的參數(shù)的loadClass:

1.首先在363行中找緩存:第一次找我們的類是沒(méi)有的
2.進(jìn)入if語(yǔ)句判斷 : parent是不等于null的,它是BootClassLoader.
3.在367行調(diào)用BootClassLoader的loadClass去加載類,如果能加載到類就直接返回這個(gè)類。
4.如果BootClassLoader沒(méi)有找到這個(gè)類,則會(huì)調(diào)用第379行進(jìn)行查找即PathClassLoader.
以上一段代碼就是雙親委托機(jī)制。
當(dāng)走到findClass的時(shí)候,就會(huì)調(diào)用PathClassLoader的findClass,而我們?cè)赑athClassLoader沒(méi)有發(fā)現(xiàn)這個(gè)方法。
同理那就找它的父類(BaseDexClassLoader ):


可以看到執(zhí)行邏輯沒(méi)有找到就報(bào)異常,找打了就返回出去。所以:關(guān)鍵看這一行代碼:pathList.findClass()。
pathList是在構(gòu)造方法里創(chuàng)建出來(lái)的。

該構(gòu)造方法里有幾個(gè)參數(shù):
1.首先是dexPath: 這是dex文件的地址。(Android類是在dex文件中的)
2.進(jìn)入DexPathList中的findClass中。

3.可以看到一個(gè)for循環(huán)操作 dexElements
4.for循環(huán)元素里面是一個(gè)Element元素,里面有一個(gè)成員變量是dexFile(dex文件)
5.循環(huán)尋找dexFile,通過(guò)dexFile去尋找我們想要的類,找到了后就返回出去了
總結(jié):在這里我們就知道andorid中尋找class文件的操作流程,進(jìn)而知道熱修復(fù)中只要把我們的修復(fù)好的class文件的dex放入到dexElements這個(gè)數(shù)組的最前面,如果找到了就直接返回出去了,就不會(huì)找到后面有問(wèn)題的class了。(優(yōu)先使用補(bǔ)丁包的類)這樣就完成了熱修復(fù)
這里寫一個(gè)簡(jiǎn)單的熱修復(fù)以便更容易清楚其原理:
制作補(bǔ)丁包流程:
1.把Bug修復(fù)掉后,先生成類的class文件
2.執(zhí)行命令

應(yīng)用補(bǔ)丁包:pathElement(補(bǔ)丁包生成的) + oldElement 賦值給oldElement
3.先獲取程序的pathClassLoader對(duì)象
4.反射獲得pathClassLoader父類的BaseDexClassLoader的pathList對(duì)象
5.反射獲取pathList的dexElements對(duì)象(oldElement)
6.把補(bǔ)丁包變成Element數(shù)組(反射執(zhí)行makePathElements)pathElement
7.合并pathElement(補(bǔ)丁包生成的) + oldElement? = newElement (Array.newInstance)
8.將新生成的newElement通過(guò)反射技術(shù)設(shè)置給回去
代碼實(shí)現(xiàn):
1.目錄構(gòu)造

該目錄結(jié)構(gòu)分為主app和lib
lib包含的內(nèi)容:PWWFix(實(shí)現(xiàn)熱修復(fù)的主要類)ShareReflectUtil(反射工具類)
主app包含的內(nèi)容:MainActivity(主要是調(diào)用Utils用于產(chǎn)生bug感).? Utils 拋出異常制造bug.? MyApplication(調(diào)用lib中的PWWFix用于修復(fù)bug)
2.MainActivity:

在MainActivity中主要是調(diào)用Utils用于產(chǎn)生bug感
3.Utils:

制造bug感以便修復(fù)
4.MyApplication:

調(diào)用修復(fù)類,解決掉bug
5.PWWFix:(主要用類加載器和反射技術(shù)將補(bǔ)丁包的elements和舊的elements 注入到一個(gè)新的newElements中,并賦值給pathlist,在這里補(bǔ)丁包的要在舊的之前,因?yàn)槌绦蚣虞d了某個(gè)類就不會(huì)再去加載器它的和它相同的類了,有緩存等)

6.ShareReflectUtil(反射工具類):

以上是相關(guān)代碼。
運(yùn)行是必定會(huì)報(bào)錯(cuò)的,因?yàn)槲覀冊(cè)趗tils中拋出了一個(gè)異常:(throw new IllegalStateException("出錯(cuò)啦?。?!"))
我們?nèi)バ迯?fù)bug,此時(shí)utils的代碼如下:

我們進(jìn)行編譯該類的代碼并打成補(bǔ)丁包:

選中

在彈出來(lái)的框中選中 Make Moudle 'app' ,待編譯完成在這個(gè)目錄下

執(zhí)行命令:(dx命令需要配置環(huán)境變量)

會(huì)發(fā)現(xiàn)會(huì)生成

把它上傳到手機(jī)sdcard這個(gè)目錄,不用運(yùn)行項(xiàng)目,直接點(diǎn)擊app:會(huì)發(fā)現(xiàn)不會(huì)崩潰了,日志會(huì)打印如下:

簡(jiǎn)單的熱修復(fù)功能至此完成。