一谣妻、背景:
如果發(fā)布了一個(gè)release包,但是里面出現(xiàn)了嚴(yán)重的事故級(jí)別(某些等不及下一版本迭代就得修復(fù))的bug卒稳,那么站在公司角度蹋半,這邊QA,開發(fā)充坑,客服减江,甚至PR等部門都得加班趕忙發(fā)包重現(xiàn)覆蓋染突,耗費(fèi)的資源代價(jià)相當(dāng)?shù)拇螅鰡栴}的或許只是一兩行代碼辈灼;站在用戶User角度份企,剛剛下載了一個(gè)包,準(zhǔn)備嘗鮮巡莹,發(fā)現(xiàn)又讓更新司志,是不是很煩,如果新包打開就崩潰了降宅,很多就會(huì)去市場評(píng)論骂远,“垃圾,更新之后閃退”云云腰根,抑或“垃圾激才,天天要更新”。
在這個(gè)基礎(chǔ)上熱更新技術(shù)出現(xiàn)了额嘿,即使發(fā)出了release包瘸恼,如果出現(xiàn)某些等不及下一版本迭代就得修復(fù)的bug,向用戶下發(fā)Patch册养,在用戶無感知的情況下东帅,修復(fù)了bug或問題。
二捕儒、類型:
花了幾天查閱一些資料冰啃,去了解一些目前的熱更新方案邓夕,主要有以下兩大類型:
1刘莹、【基于native hook方案】
參考文章:理解 Android Hook 技術(shù)以及簡單實(shí)戰(zhàn)
-
Dexposed(from 淘寶)
-
AndFix (from 支付寶)
無需重啟Application、無需啟動(dòng)Activity即可更新Java方法 安卓代碼要達(dá)到真正“熱”更新的效果焚刚,也只有基于AOP這種技術(shù)点弯,就是在方法級(jí)別這個(gè)粒度做替換。
2矿咕、【基于multidex的熱更新框架】
-
ClassLoader(originally from qq空間團(tuán)隊(duì) --原始)
在它之后和他有相同原理的其他熱門開源庫有:
-
Nuwa
-
HotFix
-
DroidFix
-
Tinker(來自微信)
-
需要重啟Activity或重啟Application達(dá)到更新效果抢肛。
3、【基于Instant Run原理】
-
Robust
前兩個(gè)都會(huì)存在一些兼容性問題碳柱,為此美團(tuán)借鑒了Instant Run原理捡絮,推出Android熱更新方案Robust
三、對(duì)比
-
基于native hook的方案:需要針對(duì)dalvik虛擬機(jī)和art虛擬機(jī)做適配莲镣,需要考慮指令集的兼容問題福稳,需要native代碼支持,兼容性上會(huì)有一定的影響瑞侮;但是無需重啟Application的圆、無需啟動(dòng)Activity即可更新Java方法鼓拧。
-
基于Multidex的方案,需要反射更改DexElements越妈,改變Dex的加載順序季俩,這使得patch需要在下次啟動(dòng)時(shí)才能生效,實(shí)時(shí)性就受到了影響梅掠,同時(shí)這種方案在android N [speed-profile]編譯模式下可能會(huì)有問題酌住,可以參考Android N混合編譯與對(duì)熱補(bǔ)丁影響解析
-
各大熱補(bǔ)丁方案分析和比較
四、具體原理
【Dexposed】
基于exposed的AOP框架瓤檐,方法級(jí)粒度赂韵,可以進(jìn)行AOP編程、插樁挠蛉、熱補(bǔ)丁祭示、SDK hook等功能。是真正程度上的熱更新谴古。無需重啟Application质涛、無需啟動(dòng)Activity即可更新Java方法£#基于AOP這種技術(shù)汇陆,就是在方法級(jí)別這個(gè)粒度做替換。
我們知道带饱,應(yīng)用啟動(dòng)的時(shí)候毡代,都會(huì)fork zygote進(jìn)程,裝載class和invoke各種初始化方法勺疼,Xposed就是在這個(gè)過程中教寂,替換了app_process,hook了各種入口級(jí)方法(比如handleBindApplication执庐、ServerThread酪耕、ActivityThread、ApplicationPackageManager的getResourcesForApplication等)轨淌,加載XposedBridge.jar提供動(dòng)態(tài)hook基礎(chǔ)迂烁。方法級(jí)的替換是指,可以在方法前递鹉、方法后插入代碼盟步,或者直接替換方法。只能針對(duì)java方法做攔截躏结,不支持C的方法却盘。
缺點(diǎn):
1.不支持AndroidRuntime(5.0+)
2.如果線上release版本進(jìn)行了混淆,那寫補(bǔ)丁也是一件很痛苦的事情,需要反射寫混淆后的代碼谷炸,粒度太細(xì)北专,要替換的方法多的話,工作量會(huì)比較大旬陡。反射+內(nèi)部類拓颓,可能還有包名和內(nèi)部類的名字沖突,總而言之就是寫得很痛苦描孟。
【AndFix】
同樣是方法的hook驶睦,AndFix不像Dexposed從Method入手,而是以Field為切入點(diǎn)匿醒。
【ClassLoader】
其他相同原理開源庫鏈接:Nuwa场航, HotFix,DroidFix廉羔,Tinker溉痢。
Classloader的原理就和上面兩個(gè)不一樣了,參考文章安卓App熱補(bǔ)丁動(dòng)態(tài)修復(fù)技術(shù)介紹憋他,以及Android dex分包方案孩饼,所以這倆篇?jiǎng)?wù)必要看。這里就不對(duì)三個(gè)框架做過多對(duì)比了竹挡,因?yàn)樵矶家恢露迫ⅲ瑢?shí)現(xiàn)的代碼可能差異并不是特別大。
總結(jié)一下就是:
一個(gè)ClassLoader可以包含多個(gè)dex文件揪罕,每個(gè)dex文件是一個(gè)Element梯码,多個(gè)dex文件排列成一個(gè)有序的數(shù)組dexElements,當(dāng)找類的時(shí)候好啰,會(huì)按順序遍歷dex文件轩娶,然后從當(dāng)前遍歷的dex文件中找類,如果找類則返回坎怪,如果找不到從下一個(gè)dex文件繼續(xù)查找罢坝。
把多個(gè)dex放進(jìn)app的classloader之中廓握,從而使得所有dex的類都能被找到搅窿。而實(shí)際上findClass的過程中,如果出現(xiàn)了重復(fù)的類隙券,改變類加載的實(shí)現(xiàn)男应,是會(huì)使用第一個(gè)找到的類的。
如果在不同的dex中有相同的類存在娱仔,那么會(huì)優(yōu)先選擇排在前面的dex文件的類沐飘。所以我們可以把有問題的類打包到一個(gè)dex(patch.dex)中去,然后把這個(gè)dex插入到Elements的最前面。
只要把有問題的類修復(fù)后耐朴,放到一個(gè)單獨(dú)的dex借卧,通過反射插入到dexElements數(shù)組的最前面,就可以讓虛擬機(jī)加載到打完補(bǔ)丁的class了筛峭。
但在實(shí)踐中铐刘,會(huì)發(fā)現(xiàn)運(yùn)行加載類的時(shí)候報(bào)preverified錯(cuò)誤,原來在DexPrepare.cpp影晓,將dex轉(zhuǎn)化成odex的過程中镰吵,會(huì)在DexVerify.cpp進(jìn)行校驗(yàn),驗(yàn)證如果直接引用到的類和clazz是否在同一個(gè)dex挂签,如果是疤祭,則會(huì)打上CLASS_ISPREVERIFIED標(biāo)志。通過在所有類(Application除外饵婆,當(dāng)時(shí)還沒加載自定義類的代碼)的構(gòu)造函數(shù)插入一個(gè)對(duì)在單獨(dú)的dex的類的引用勺馆,就可以解決這個(gè)問題∏群耍空間使用了javaassist進(jìn)行編譯時(shí)字節(jié)碼插入谓传。總結(jié)起來就是兩點(diǎn)芹关,第一是動(dòng)態(tài)改變BaseDexClassLoader對(duì)象間接引用的dexElements续挟,第二是在app打包的時(shí)候,阻止相關(guān)類去打上CLASS_ISPREVERIFIED標(biāo)志侥衬。