從零開(kāi)始初步實(shí)現(xiàn)安卓熱修復(fù)功能

熱修復(fù)兢仰,即熱加載阳似。是指在一個(gè)App正常打開(kāi)之后粗卜,從外部加載本不屬于它的一些資源并加以運(yùn)用的功能。以下demo只是基于熱修復(fù)的原理做到了初步實(shí)現(xiàn)瘤睹,另一個(gè)關(guān)鍵點(diǎn) dex插樁 還有待下一篇文章詳細(xì)講解,因此這一版的demo并不能投入實(shí)際的商業(yè)化運(yùn)用

熱修復(fù)的實(shí)現(xiàn)的途徑有很多種答倡,其中一種是通過(guò)反射來(lái)拿到當(dāng)前應(yīng)用程序的 ClassLoader 中的成員變量:pathList 所指向的實(shí)例轰传。然后再通過(guò)反射這個(gè)實(shí)例,拿到其內(nèi)部成員變量:dexElements數(shù)組 的值瘪撇。由于安卓系統(tǒng)在加載一個(gè)類之前會(huì)從前往后地去遍歷這個(gè) dexElements數(shù)組获茬,尋找當(dāng)前需要加載的 .class 文件。因此倔既,只要我們將外部下載的 apk包 或者是 .dex 文件路徑生成的對(duì)象插入到 dexElements數(shù)組 的最前方恕曲,就可以達(dá)到頂替數(shù)組后面原有的同名 .class 文件的目的,從而實(shí)現(xiàn)外部熱修復(fù)渤涌。示意圖如下(patch.dex就是外部加載進(jìn)來(lái)的修復(fù)包):


因此,在整個(gè)熱加載功能實(shí)現(xiàn)過(guò)程中(包括插件化和so庫(kù)動(dòng)態(tài)加載):Classloader -> 內(nèi)部DexPathList類型的成員變量 -> DexPathList中的成員變量Elements數(shù)組的更改吊履,是整個(gè)功能中比較核心的一環(huán)腾窝,運(yùn)用類加載的實(shí)現(xiàn)方式完成熱修復(fù)的框架,源碼中一定有反射并修改elements數(shù)組的相關(guān)邏輯托慨。

根據(jù)以上提到的思路,我們來(lái)看一下核心的相關(guān)代碼:

拿到 ClassLoader 和 修復(fù)包的存儲(chǔ)路徑 之后奸例,就可以開(kāi)始準(zhǔn)備做 java反射了,這一塊的邏輯我封裝到了 installSecondaryDexes 方法中

根據(jù)不同的系統(tǒng)SDK版本執(zhí)行不同的反射邏輯(我們以 api19 及其以上的代碼為例)

接下來(lái)獲取到 dexElements 數(shù)組炼杖,準(zhǔn)備在數(shù)組第一位插入外部載入的修復(fù)包路徑

由上圖可見(jiàn)罩扇,截圖中的最后一個(gè)方法 makeDexElements 就是插入外部修復(fù)包路徑的封裝方法,這個(gè)方法的第三個(gè)實(shí)參又是一個(gè)方法,這個(gè)方法是根據(jù)不同的手機(jī)SDK版本氯材,將修復(fù)包的路徑封裝成不同類型的對(duì)象組成的數(shù)組,這個(gè)數(shù)組最后會(huì)被插入到 dexElements 數(shù)組當(dāng)中。經(jīng)過(guò)了 expandFieldArray 這個(gè)方法的執(zhí)行之后,原先存儲(chǔ) 外部修復(fù)包 的路徑下就多出了一個(gè) .dex 文件,或者 .odex 和 .vdex 文件。makeDexElements 方法的代碼如下:

我們?cè)倩氐酵鈱拥?expandFieldArray 方法澎埠,其內(nèi)部的邏輯是這樣的:首先通過(guò)反射拿到dexElements的取值,然后將上圖方法獲取到的 object[] 插入到數(shù)組的最前面诉植。這個(gè)被插入的 object[] 數(shù)組就是外部修復(fù)包存儲(chǔ)路徑集合編譯后形成的隊(duì)列啊犬,也就是外部修復(fù)包的資源和 .class 隊(duì)列

以上步驟完成之后,當(dāng)前App的 dexElements 的狀態(tài)就變成了這樣壁查,理論上是可以頂替原包的同名 .class 文件了:

接下來(lái)我們做一些外部的封裝觉至,比如說(shuō)斷點(diǎn)續(xù)傳下載外部修復(fù)包,以及在Splash頁(yè)面上做熱修復(fù)準(zhǔn)備處理等等睡腿,original包和fix包的代碼都在下面的git鏈接上了:

https://github.com/liuchenguangqnm/hot_fix_example

然后我們?cè)僭趥z包的MainActivity上分別寫(xiě)上不同的吐司顯示準(zhǔn)備測(cè)試:

然后找到 original_package 的 Appconfig 類语御,配置好修復(fù)包的下載地址,以及下載到本地的存儲(chǔ)文件名

最后就可以安裝看效果了:

首先第一次打開(kāi)席怪,因?yàn)樾迯?fù)包需要下載時(shí)間应闯,所以第一次打開(kāi)App或者斷網(wǎng)的時(shí)候修復(fù)包是沒(méi)有插入完成的,所以此時(shí)點(diǎn)擊按鈕提示如下:

等到第一次的修復(fù)包插入完成之后挂捻,關(guān)閉MainActivity碉纺,再次打開(kāi),點(diǎn)擊測(cè)試按鈕细层,吐司顯示如下:

以上代碼經(jīng)本人測(cè)試惜辑,可以順利在安卓各個(gè)模擬器和小米4上面跑通。由于開(kāi)篇的時(shí)候我就說(shuō)過(guò)了疫赎,此版本demo由于沒(méi)有做 dex插樁盛撑,因此不能投入商業(yè)化使用,所以捧搞,以上的demo在華為手機(jī)上是跑不通的抵卫。

那么什么是dex插樁呢?

我們首先圍繞上面講到的問(wèn)題出發(fā):為什么有的手機(jī)按照demo上的代碼運(yùn)行可以正常走通胎撇,有些就走不通介粘,難道有的手機(jī)加載類的時(shí)候不是通過(guò)遍歷 dexElements 數(shù)組獲得 .class 文件的嗎?當(dāng)然這是不可能的晚树。以上demo之所以走不通姻采,是因?yàn)樾掳姹镜氖謾C(jī)SDK為了提高App的啟動(dòng)速度,已經(jīng)在我們安裝應(yīng)用的時(shí)候預(yù)先加載了安裝包里的所有 .class 文件的索引爵憎,有了這個(gè)索引慨亲,我們?cè)俅蜷_(kāi)App的時(shí)候,系統(tǒng)就再不用去 遍歷DexElements數(shù)組 尋找對(duì)應(yīng)的 .class 文件了宝鼓。

因?yàn)槊看渭虞d這個(gè)類刑棵,新安卓版本的系統(tǒng)都不用再去遍歷 DexElements數(shù)組 了,因此我們?cè)?App啟動(dòng)之后愚铡,再去對(duì) DexElements數(shù)組 的內(nèi)容進(jìn)行操作蛉签,其實(shí)都是無(wú)意義的,因?yàn)橄到y(tǒng)根本不會(huì)再次遍歷它了。而解決這個(gè)問(wèn)題的途徑之一就是 dex插樁碍舍!

首先柠座,我們要清楚的是,系統(tǒng)在安裝了一個(gè)新的App之后乒验,首先會(huì)查看這個(gè) App 里面有多少 .dex 文件愚隧。如果一個(gè) .class 文件里面使用到的所有類,都在一個(gè) .dex 文件之中存放的話锻全,那么系統(tǒng)就會(huì)給這個(gè)類打上一個(gè) CLASS_ISPREVERIFIED 標(biāo)記狂塘,有了這個(gè)標(biāo)記,下次App如果要再加載這個(gè)類的時(shí)候鳄厌,就不會(huì)再次遍歷 DexElements 數(shù)組了荞胡;如果一個(gè) .class 文件里面使用到的所有類了嚎,分別在兩個(gè) .dex 文件之中的話泪漂,那么為了避免在類運(yùn)行過(guò)程中,因?yàn)槠渲幸粋€(gè) .dex 文件的缺失而導(dǎo)致異常歪泳,此時(shí)這個(gè)類不會(huì)被打上 CLASS_ISPREVERIFIED 標(biāo)記萝勤,每次我們要加載它的時(shí)候,系統(tǒng)還是要遍歷一次 dexElements 數(shù)組敌卓,確保所有的 .dex 文件都是全乎的

因此,我們需要實(shí)現(xiàn)的就是,在安卓打包的時(shí)候级遭,給所有有可能出bug的類的構(gòu)造方法里面都插入一行初始化其它 .dex 文件中的類型的代碼掠兄,只要有了這行代碼侈贷,這個(gè)類在每次加載之前就必須要重新過(guò)一遍 dexElements 數(shù)組,我們的修復(fù)包就可以趁著這個(gè)時(shí)機(jī),頂?shù)舸嬖赽ug的類燃异。這種在打包時(shí)修改 .class 文件內(nèi)容的技術(shù)鳄逾,就叫做 dex插樁

由于時(shí)間和精力有限,再加上dex插樁技術(shù)本人也需要花更多的時(shí)間去測(cè)試和研究苦锨,因此我會(huì)在下一篇博客繼續(xù)完善上面分享過(guò)的demo

以上氏仗,本篇完結(jié)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市砌们,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖成艘,帶你破解...
    沈念sama閱讀 212,599評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異丹莲,居然都是意外死亡甥材,警方通過(guò)查閱死者的電腦和手機(jī)苛谷,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,629評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門(mén)腹殿,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)决采,“玉大人孝偎,你說(shuō)我怎么就攤上這事邪媳。” “怎么了荡陷?”我有些...
    開(kāi)封第一講書(shū)人閱讀 158,084評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵雨效,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我废赞,道長(zhǎng)徽龟,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,708評(píng)論 1 284
  • 正文 為了忘掉前任唉地,我火速辦了婚禮据悔,結(jié)果婚禮上传透,老公的妹妹穿的比我還像新娘。我一直安慰自己极颓,他們只是感情好朱盐,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,813評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著菠隆,像睡著了一般兵琳。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上骇径,一...
    開(kāi)封第一講書(shū)人閱讀 50,021評(píng)論 1 291
  • 那天躯肌,我揣著相機(jī)與錄音,去河邊找鬼破衔。 笑死清女,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的晰筛。 我是一名探鬼主播嫡丙,決...
    沈念sama閱讀 39,120評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼传惠!你這毒婦竟也來(lái)了迄沫?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 37,866評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤卦方,失蹤者是張志新(化名)和其女友劉穎羊瘩,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體盼砍,經(jīng)...
    沈念sama閱讀 44,308評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡尘吗,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,633評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了浇坐。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片睬捶。...
    茶點(diǎn)故事閱讀 38,768評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖近刘,靈堂內(nèi)的尸體忽然破棺而出擒贸,到底是詐尸還是另有隱情,我是刑警寧澤觉渴,帶...
    沈念sama閱讀 34,461評(píng)論 4 333
  • 正文 年R本政府宣布介劫,位于F島的核電站,受9級(jí)特大地震影響案淋,放射性物質(zhì)發(fā)生泄漏座韵。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,094評(píng)論 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望誉碴。 院中可真熱鬧宦棺,春花似錦、人聲如沸黔帕。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,850評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)成黄。三九已至侣背,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間慨默,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,082評(píng)論 1 267
  • 我被黑心中介騙來(lái)泰國(guó)打工弧腥, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留厦取,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,571評(píng)論 2 362
  • 正文 我出身青樓管搪,卻偏偏與公主長(zhǎng)得像虾攻,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子更鲁,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,666評(píng)論 2 350

推薦閱讀更多精彩內(nèi)容