2015年以來,Android開發(fā)領(lǐng)域里對(duì)熱修復(fù)技術(shù)的討論和分享越來越多,同時(shí)也出現(xiàn)了一些不同的解決方案,如QQ空間補(bǔ)丁方案、阿里AndFix以及微信Tinker(Bugly sdk也集成Tikner熱更新)和阿里最新出品Sophix.它們?cè)谠砀饔胁煌m用場(chǎng)景各異,到底采用哪種方案,是開發(fā)者比較頭疼的問題。下面是這幾種技術(shù)方案介紹。
技術(shù)背景

一、正常開發(fā)流程
從流程來看,傳統(tǒng)的開發(fā)流程存在很多弊端:
·重新發(fā)布版本代價(jià)太大
·用戶下載安裝成本太高
·
BUG修復(fù)不及時(shí),用戶體驗(yàn)太差
二、熱修復(fù)開發(fā)流程

而熱修復(fù)的開發(fā)流程顯得更加靈活,優(yōu)勢(shì)很多:
·無需重新發(fā)版,實(shí)時(shí)高效熱修復(fù)
·用戶無感知修復(fù),無需下載新的應(yīng)用,代價(jià)小
·修復(fù)成功率高,把損失降到最低
業(yè)界熱門的熱修復(fù)技術(shù)
—
熱修復(fù)作為當(dāng)下熱門的技術(shù),在業(yè)界內(nèi)比較著名的有阿里巴巴的AndFix、Dexposed,騰訊QQ空間的超級(jí)補(bǔ)丁和微信的Tinker。最近阿里百川推出的HotFix熱修復(fù)服務(wù)就基于AndFix技術(shù),定位于線上緊急BUG的即時(shí)修復(fù),所以AndFix技術(shù)這塊我們重點(diǎn)分析阿里百川HotFix。下面,我們就分別介紹QQ空間超級(jí)熱補(bǔ)丁技術(shù)和微信Tinker以及阿里百川的HotFix技術(shù)。
一、QQ空間超級(jí)補(bǔ)丁技術(shù)

超級(jí)補(bǔ)丁技術(shù)基于DEX分包方案,使用了多DEX加載的原理,大致的過程就是:把BUG方法修復(fù)以后,放到一個(gè)單獨(dú)的DEX里,插入到dexElements數(shù)組的最前面,讓虛擬機(jī)去加載修復(fù)完后的方法
當(dāng)patch.dex中包含Test.class時(shí)就會(huì)優(yōu)先加載,在后續(xù)的DEX中遇到Test.class的話就會(huì)直接返回而不去加載,這樣就達(dá)到了修復(fù)的目的。
但是有一個(gè)問題是,當(dāng)兩個(gè)調(diào)用關(guān)系的類不在同一個(gè)DEX時(shí),就會(huì)產(chǎn)生異常報(bào)錯(cuò)。我們知道,在APK安裝時(shí),虛擬機(jī)需要將classes.dex優(yōu)化成odex文件,然后才會(huì)執(zhí)行。在這個(gè)過程中,會(huì)進(jìn)行類的verify操作,如果調(diào)用關(guān)系的類都在同一個(gè)DEX中的話就會(huì)被打上`CLASS_ISPREVERIFIED`的標(biāo)志,然后才會(huì)寫入odex文件。
所以,為了可以正常地進(jìn)行打補(bǔ)丁修復(fù),必須避免類被打上`CLASS_ISPREVERIFIED`標(biāo)志,具體的做法就是單獨(dú)放一個(gè)類在另外DEX中,讓其他類調(diào)用。
修復(fù)的步驟為:
1.可以看出是通過獲取到當(dāng)前應(yīng)用的Classloader,即為BaseDexClassloader
2.通過反射獲取到他的DexPathList屬性對(duì)象pathList
3.通過反射調(diào)用pathList的dexElements方法把patch.dex轉(zhuǎn)化為Element[]
4.兩個(gè)Element[]進(jìn)行合并,把patch.dex放到最前面去
5.加載Element[],達(dá)到修復(fù)目的
整體的流程圖如下:

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

整體的流程如下:

從流程圖來看,同樣可以很明顯的找到這種方式的特點(diǎn):
優(yōu)勢(shì):
1.合成整包,不用在構(gòu)造函數(shù)插入代碼,防止verify,verify和opt在編譯期間就已經(jīng)完成,不會(huì)在運(yùn)行期間進(jìn)行。
2.性能提高。兼容性和穩(wěn)定性比較高。
3.開發(fā)者透明,不需要對(duì)包進(jìn)行額外處理。
不足:
1.與超級(jí)補(bǔ)丁技術(shù)一樣,不支持即時(shí)生效,必須通過重啟應(yīng)用的方式才能生效。
2.需要給應(yīng)用開啟新的進(jìn)程才能進(jìn)行合并,并且很容易因?yàn)閮?nèi)存消耗等原因合并失敗。
3.合并時(shí)占用額外磁盤空間,對(duì)于多DEX的應(yīng)用來說,如果修改了多個(gè)DEX文件,就需要下發(fā)多個(gè)patch.dex與對(duì)應(yīng)的classes.dex進(jìn)行合并操作時(shí)這種情況會(huì)更嚴(yán)重,因此合并過程的失敗率也會(huì)更高。
三、阿里百川HotFix
阿里百川推出的熱修復(fù)HotFix服務(wù),相對(duì)于QQ空間超級(jí)補(bǔ)丁技術(shù)和微信Tinker來說,定位于緊急BUG修復(fù)的場(chǎng)景下,能夠最及時(shí)的修復(fù)BUG,下拉補(bǔ)丁立即生效無需等待。

1、AndFix實(shí)現(xiàn)原理
AndFix不同于QQ空間超級(jí)補(bǔ)丁技術(shù)和微信Tinker通過增加或替換整個(gè)DEX的方案,提供了一種運(yùn)行時(shí)在Native修改Filed指針的方式,實(shí)現(xiàn)方法的替換,達(dá)到即時(shí)生效無需重啟,對(duì)應(yīng)用無性能消耗的目的。
原理圖如下:

2、AndFix實(shí)現(xiàn)過程
對(duì)于實(shí)現(xiàn)方法的替換,需要在Native層操作,經(jīng)過三個(gè)步驟:

AndFix對(duì)ART設(shè)備支持,過程與Dalvik相似。
從技術(shù)原理,不難看出阿里百川HotFix的幾個(gè)特點(diǎn):
優(yōu)勢(shì):
1.
BUG修復(fù)的即時(shí)性
2.補(bǔ)丁包同樣采用差量技術(shù),生成的PATCH體積小
3.對(duì)應(yīng)用無侵入,幾乎無性能損耗
不足:
1.不支持新增字段,以及修改方法,也不支持對(duì)資源的替換。
2.由于廠商的自定義ROM,對(duì)少數(shù)機(jī)型暫不支持。兼容性差。
(每一個(gè)java方法在art種都對(duì)應(yīng)一個(gè)ArtMethod, ArtMethod記錄了這個(gè)java方法的所有信息,包括所屬類,訪問權(quán)限、代碼執(zhí)行地址。
通過evn->FromReflectedMethod,可以由Method對(duì)象得到這個(gè)方法對(duì)應(yīng)的ArtMethod的真正其實(shí)地址,然后就可以把它強(qiáng)轉(zhuǎn)為ArtMethod指針,從而對(duì)其所有的成員進(jìn)行修改。這樣就全部替換完之后就完成了熱修復(fù)邏輯。以后調(diào)用這個(gè)方法時(shí)就會(huì)直接走到新方法的實(shí)現(xiàn)中了。
然而由于andfix里面的ArtMethod的結(jié)構(gòu)體遵照android虛擬機(jī)art源碼里面的ArtMethod構(gòu)建的,各個(gè)手機(jī)廠商對(duì)這個(gè)ArtMethod結(jié)構(gòu)體進(jìn)行修改就會(huì)導(dǎo)致喝原來開源代碼里面的結(jié)構(gòu)不一致,那么在這個(gè)修改過的設(shè)備上,替換機(jī)制就會(huì)出問題,無法正常執(zhí)行熱修復(fù)邏輯。)
綜合分析如下:

對(duì)比總結(jié):
一、qq空間超級(jí)補(bǔ)丁,微信Tinker類似多DEX帶來的性能影響
我們知道,多DEX方案原來是用于解決應(yīng)用方法數(shù)65k的問題,現(xiàn)在google也官方支持了MultiDex的實(shí)現(xiàn)方案。超級(jí)補(bǔ)丁技術(shù)和Tinker卻作為一種熱修復(fù)的方案,平生給應(yīng)用增加了多個(gè)DEX,而多DEX技術(shù)最大的問題在于性能上的坑,因此基于這種方案的補(bǔ)丁技術(shù)影響應(yīng)用的性能是無疑的。
1.啟動(dòng)加載時(shí)間過長(zhǎng)
我們可以看到,超級(jí)補(bǔ)丁技術(shù)和Tinker都選擇在Application的attachBaseContext()進(jìn)行補(bǔ)丁dex的加載,即時(shí)這是加載dex的最佳時(shí)機(jī),但是依然會(huì)帶來很大的性能問題,首當(dāng)其沖的就是啟動(dòng)時(shí)間太長(zhǎng)。
對(duì)于補(bǔ)丁DEX來說,應(yīng)用啟動(dòng)時(shí)虛擬機(jī)會(huì)進(jìn)行dexopt操作,將patch.dex文件轉(zhuǎn)換成odex文件,這個(gè)過程本身非常耗時(shí)。而這個(gè)過程又要求在主線程中,以同步的方式執(zhí)行,否則無法成功進(jìn)行修復(fù)。就DEX的加載時(shí)間,大概做了以下的時(shí)間測(cè)試。

通過上表可以看到,隨著patch.dex的尺寸增加,在不做任何優(yōu)化的情況下,啟動(dòng)時(shí)間也直線增長(zhǎng)。對(duì)于一個(gè)應(yīng)用來說,這簡(jiǎn)直是災(zāi)難性的。
2.易造成應(yīng)用的ANR和Crash
由于多DEX加載導(dǎo)致了啟動(dòng)時(shí)間變長(zhǎng),這樣更容易引發(fā)應(yīng)用的ANR。我們知道當(dāng)應(yīng)用在主線程等待超過5s以后,就會(huì)直接導(dǎo)致長(zhǎng)時(shí)間無響應(yīng)而退出。超級(jí)補(bǔ)丁技術(shù)為保證ART不出現(xiàn)地址錯(cuò)亂問題,需要將所有關(guān)聯(lián)的類全部加入到補(bǔ)丁中,而微信Tinker采取一種差量包合并加載的方式,都會(huì)使要加載的DEX體積變得很大。這也很大程度上容易導(dǎo)致ANR情況的出現(xiàn)。
除了應(yīng)用ANR以外,多DEX模式也同樣很容易導(dǎo)致Crash情況的出現(xiàn)。在ART設(shè)備中為了保證不出現(xiàn)地址錯(cuò)亂,需要把修改類的所有相關(guān)類全部加入到補(bǔ)丁中,這里會(huì)出現(xiàn)一個(gè)問題,為了保證補(bǔ)丁包的體積最小,能否保證引入全部的關(guān)聯(lián)類而不引入無關(guān)的類呢?一旦沒有引入關(guān)聯(lián)的類,就會(huì)出現(xiàn)以下的異常:
·NoClassDefFoundError
·Could Not Find Class
·Could Not Find Method
出現(xiàn)這些異常,就會(huì)直接導(dǎo)致應(yīng)用的Crash退出。
所以,不難看出如果我們需要修復(fù)一個(gè)不是Crash的BUG,但是因?yàn)槲醇尤胂嚓P(guān)類而導(dǎo)致了更嚴(yán)重的Crash,就更加的得不償失。
總的來說,熱修復(fù)本質(zhì)的目的是為了保證應(yīng)用更加穩(wěn)定,而不是為了更強(qiáng)大的功能引入更大的風(fēng)險(xiǎn)和不穩(wěn)定性。
二、熱修復(fù)or插件化?
我們經(jīng)常提到熱修復(fù)和插件化,這都是當(dāng)下熱門的新興技術(shù)。在講述之前,需要對(duì)這兩個(gè)概念進(jìn)行一下解釋。
·熱修復(fù):當(dāng)線上應(yīng)用出現(xiàn)緊急BUG,為了避免重新發(fā)版,并且保證修復(fù)的及時(shí)性而進(jìn)行的一項(xiàng)在線推送補(bǔ)丁的修復(fù)方案。
·插件化:一個(gè)程序劃分為不同的部分,以插件的形式加載到應(yīng)用中去,本質(zhì)上它使用的技術(shù)還是熱修復(fù)技術(shù),只是加入了更多工程實(shí)踐,讓它支持大規(guī)模的代碼更新以及資源和SO包的更新。
顯然,從概念上我們可以看到,插件化使用場(chǎng)景更多是功能上的,熱修復(fù)強(qiáng)調(diào)微小的修復(fù)。從這個(gè)層面來說,插件化必然功能更加強(qiáng)大,能做的事情也更多。QQ空間超級(jí)補(bǔ)丁技術(shù)和微信Tinker從類、資源的替換和更新上來看,與其說是熱修復(fù),不如說是插件化技術(shù)的實(shí)踐。
QQ空間超級(jí)補(bǔ)丁技術(shù)和微信Tinker提供了更加強(qiáng)大的功能,但是對(duì)應(yīng)用的性能和穩(wěn)定有較大的影響,就BUG修復(fù)的這個(gè)使用場(chǎng)景上還不夠明確,并且顯得過重。在插件化開發(fā)上,有用武之地。
同樣andfix兼容又有很大的問題。
終于進(jìn)入主題阿里最新推出的熱修復(fù)方案技術(shù)sophfix.
Sophix設(shè)計(jì)理念:
Sophix的核心設(shè)計(jì)理念,就是非侵入性。
我們的打包過程不會(huì)侵入到apk的build流程中。我們所需要的,只有已經(jīng)生成完畢的新舊apk,而至于apk是如何生成的——是Android
Studio打包出來的、還是Eclipse打包出來的、或者是自定義的打包流程,我們一律不關(guān)心。在生成補(bǔ)丁的過程中間既不會(huì)改變?nèi)魏未虬M件,也不插入任何AOP代碼,我們極力做到了——不添加任何超出開發(fā)者預(yù)期的代碼,以避免多余的熱修復(fù)代碼給開發(fā)者帶來困擾。
在Sophix中,唯一需要的就是初始化和請(qǐng)求補(bǔ)丁兩行代碼,甚至連入口Application類我們都不做任何修改,這樣就給了開發(fā)者最大的透明度和自由度。我們甚至重新開發(fā)了打包工具,使得補(bǔ)丁工具操作圖形界面化,這種所見即所得的補(bǔ)丁生成方式也是阿里熱修復(fù)獨(dú)家的。因此,Sophix的接入成本也是目前市面上所有方案里最低的。
這種非侵入式熱更新理念,是我們?cè)谠O(shè)計(jì)過程中從用戶使用角度進(jìn)行了深入思考而提煉出的核心思想。
這里的用戶,指的自然是廣大的開發(fā)者。對(duì)于開發(fā)者而言,熱修復(fù)應(yīng)該是一個(gè)與業(yè)務(wù)無關(guān)的SDK組件,在整個(gè)開發(fā)過程中感知不到它的存在。最理想的情況,就是開發(fā)者拿過來兩個(gè)apk,一個(gè)是已經(jīng)安裝在手機(jī)上的apk,另一個(gè)是將要發(fā)布出去的apk。我們直接通過工具,就可以根據(jù)這兩個(gè)apk生成補(bǔ)丁,然后把這個(gè)補(bǔ)丁下發(fā)給已經(jīng)安裝的舊app上,就可以直接加載,使舊app重生為新的app。而這個(gè)加載了補(bǔ)丁包新app,在功能和使用上,將會(huì)和直接安裝新apk別無二致。
Sophfix與其他熱修復(fù)方案對(duì)比:

可以看到,Sophix在各個(gè)指標(biāo)上全面占優(yōu)。而其中唯一不支持的地方就是四大組件的修復(fù),這是因?yàn)槿绻迯?fù)四大組件,必須在AndroidManifest里面預(yù)先插入代理組件,并且盡可能聲明所有權(quán)限,而這么做就會(huì)給原先的app添加很多臃腫的代碼,對(duì)app運(yùn)行流程的侵入性很強(qiáng)。
Sophix支持代碼修復(fù)、資源修復(fù)、so庫修復(fù)。下面對(duì)這三種修復(fù)進(jìn)行介紹。
一、代碼修復(fù)
代碼修復(fù)有兩大主要方案,一種是阿里系的底層替換方案,另一種是騰訊系的類加載方案。
這兩類方案各有優(yōu)劣:
底層替換方案限制頗多,但時(shí)效性最好,加載輕快,立即見效。
類加載方案時(shí)效性差,需要重新冷啟動(dòng)才能見效,但修復(fù)范圍廣,限制少。
底層替換方案
底層替換方案是在已經(jīng)加載了的類中直接替換掉原有方法,是在原來類的基礎(chǔ)上進(jìn)行修改的。因而無法實(shí)現(xiàn)對(duì)與原有類進(jìn)行方法和字段的增減,因?yàn)檫@樣將破壞原有類的結(jié)構(gòu)。
一旦補(bǔ)丁類中出現(xiàn)了方法的增加和減少,就會(huì)導(dǎo)致這個(gè)類以及整個(gè)Dex的方法數(shù)的變化。方法數(shù)的變化伴隨著方法索引的變化,這樣在訪問方法時(shí)就無法正常地索引到正確的方法了。
如果字段發(fā)生了增加和減少,和方法變化的情況一樣,所有字段的索引都會(huì)發(fā)生變化。并且更嚴(yán)重的問題是,如果在程序運(yùn)行中間某個(gè)類突然增加了一個(gè)字段,那么對(duì)于原先已經(jīng)產(chǎn)生的這個(gè)類的實(shí)例,它們還是原來的結(jié)構(gòu),這是無法改變的。而新方法使用到這些老的實(shí)例對(duì)象時(shí),訪問新增字段就會(huì)產(chǎn)生不可預(yù)期的結(jié)果。
這是這類方案的固有限制,而底層替換方案最為人詬病的地方,在于底層替換的不穩(wěn)定性。
傳統(tǒng)的底層替換方式,不論是Dexposed、Andfix或者其他安全界的Hook方案,都是直接依賴修改虛擬機(jī)方法實(shí)體的具體字段。例如,改Dalvik方法的jni函數(shù)指針、改類或方法的訪問權(quán)限等等。這樣就帶來一個(gè)很嚴(yán)重的問題,由于Android是開源的,各個(gè)手機(jī)廠商都可以對(duì)代碼進(jìn)行改造,而Andfix里ArtMethod的結(jié)構(gòu)是根據(jù)公開的Android源碼中的結(jié)構(gòu)寫死的。如果某個(gè)廠商對(duì)這個(gè)ArtMethod結(jié)構(gòu)體進(jìn)行了修改,就和原先開源代碼里的結(jié)構(gòu)不一致,那么在這個(gè)修改過了的設(shè)備上,通用性的替換機(jī)制就會(huì)出問題。這便是不穩(wěn)定的根源。
而我們也對(duì)代碼的底層替換原理重新進(jìn)行了深入思考,從克服其限制和兼容性入手,以一種更加優(yōu)雅的替換思路,實(shí)現(xiàn)了即時(shí)生效的代碼熱修復(fù)。sophix實(shí)現(xiàn)的是一種無視底層具體結(jié)構(gòu)的替換方式,也就是把原先這樣的逐一替換:

變成了這樣的整體替換:

這么一來,我們不僅解決了兼容性問題,并且由于忽略了底層ArtMethod結(jié)構(gòu)的差異,對(duì)于所有的Android版本都不再需要區(qū)分,代碼量大大減少。即使以后的Android版本不斷修改ArtMethod的成員,只要保證ArtMethod數(shù)組仍是以線性結(jié)構(gòu)排列,就能直接適用于將來的Android 8.0、9.0等新版本,無需再針對(duì)新的系統(tǒng)版本進(jìn)行適配了。
事實(shí)也證明確實(shí)如此,當(dāng)我們拿到Google剛發(fā)不久的Android O(8.0)開發(fā)者預(yù)覽版的系統(tǒng)時(shí),hotfix demo直接就能順利地加載補(bǔ)丁跑起來了,我們并沒有做任何適配工作,穩(wěn)定性極好。
類加載方案
類加載方案的原理是在app重新啟動(dòng)后讓Classloader去加載新的類。因?yàn)樵赼pp運(yùn)行到一半的時(shí)候,所有需要發(fā)生變更的類已經(jīng)被加載過了,在Android上是無法對(duì)一個(gè)類進(jìn)行卸載的。如果不重啟,原來的類還在虛擬機(jī)中,就無法加載新類。因此,只有在下次重啟的時(shí)候,在還沒走到業(yè)務(wù)邏輯之前搶先加載補(bǔ)丁中的新類,這樣后續(xù)訪問這個(gè)類時(shí),就會(huì)Resolve為新類。從而達(dá)到熱修復(fù)的目的。
再來看看騰訊系三大類加載方案的實(shí)現(xiàn)原理。QQ空間方案會(huì)侵入打包流程,并且為了hack添加一些無用的信息,實(shí)現(xiàn)起來很不優(yōu)雅。而QFix的方案,需要獲取底層虛擬機(jī)的函數(shù),不夠穩(wěn)定可靠,并且有個(gè)比較大的問題是無法新增public函數(shù)。
微信的Tinker方案是完整的全量dex加載,并且可謂是將補(bǔ)丁合成做到了極致,然而我們發(fā)現(xiàn),精密的武器并非適用于所有戰(zhàn)場(chǎng)。Tinker的合成方案,是從dex的方法和指令維度進(jìn)行全量合成,整個(gè)過程都是自己研發(fā)的。
雖然可以很大地節(jié)省空間,但由于對(duì)dex內(nèi)容的比較粒度過細(xì),實(shí)現(xiàn)較為復(fù)雜,性能消耗比較嚴(yán)重。實(shí)際上,dex的大小占整個(gè)apk的比例是比較低的,一個(gè)app里面的dex文件大小并不是主要部分,而占空間大的主要還是資源文件。因此,Tinker方案的時(shí)空代價(jià)轉(zhuǎn)換的性價(jià)比不高。
其實(shí),dex比較的最佳粒度,應(yīng)該是在類的維度。它既不像方法和指令維度那樣的細(xì)微,也不像bsbiff比較那般的粗糙。在類的維度,可以達(dá)到時(shí)間和空間平衡的最佳效果?;谶@個(gè)準(zhǔn)則,我們另辟蹊徑,實(shí)現(xiàn)了一種完全不同的全量dex替換方案。
sophix采用的也是全量合成dex的技術(shù),這個(gè)技術(shù)是從手淘插件化框架Atlas汲取的。直接利用Android原先的類查找和合成機(jī)制,快速合成新的全量dex。這么一來,我們既不需要處理合成時(shí)方法數(shù)超過的情況,對(duì)于dex的結(jié)構(gòu)也不用進(jìn)行破壞性重構(gòu)。

從圖中可以看到,我們重新編排了包中dex的順序。這樣,在虛擬機(jī)查找類的時(shí)候,會(huì)優(yōu)先找到classes.dex中的類,然后才是classes2.dex、classes3.dex,也可以看做是dex文件級(jí)別的類插樁方案。這個(gè)方式十分巧妙,它對(duì)舊包與補(bǔ)丁包中classes.dex的順序進(jìn)行了打破與重組,最終使得系統(tǒng)可以自然地識(shí)別到這個(gè)順序,以實(shí)現(xiàn)類覆蓋的目的。這將會(huì)大大減少合成補(bǔ)丁的開銷。
雙劍合璧
既然底層替換方案和類加載方案各有其優(yōu)點(diǎn),把他們聯(lián)合起來不是最好的選擇嗎?Sophix的代碼修復(fù)體系正是同時(shí)涵蓋了這兩種方案。兩種方案的結(jié)合,可以實(shí)現(xiàn)優(yōu)勢(shì)互補(bǔ),完全兼顧的作用,可以靈活地根據(jù)實(shí)際情況自動(dòng)切換。
這兩種方案我們都進(jìn)行了重大的改進(jìn),并且從補(bǔ)丁生成到應(yīng)用的各個(gè)環(huán)節(jié)都進(jìn)行了研究,使得二者能很好地整合在一起。在補(bǔ)丁生成階段,補(bǔ)丁工具會(huì)根據(jù)實(shí)際代碼變動(dòng)情況進(jìn)行自動(dòng)選擇,針對(duì)小修改,在底層替換方案限制范圍內(nèi)的,就直接采用底層替換修復(fù)嗎,這樣可以做到代碼修復(fù)即時(shí)生效。而對(duì)于代碼修改超出底層替換限制的,會(huì)使用類加載替換,這樣雖然及時(shí)性沒那么好,但總歸可以達(dá)到熱修復(fù)的目的。
另外,運(yùn)行時(shí)階段,Sophix還會(huì)再判斷所運(yùn)行的機(jī)型是否支持熱修復(fù),這樣即使補(bǔ)丁支持熱修復(fù),但由于機(jī)型底層虛擬機(jī)構(gòu)造不支持,還是會(huì)走類加載修復(fù),從而達(dá)到最好的兼容性。最后也要注意
二、資源修復(fù)
不android資源熱修復(fù),就是在app不重新安裝的情況下,利用下發(fā)補(bǔ)丁包直接更新本app中的資源。
目前市面上的資源熱修復(fù)方案基本上都是參考Instant Run的實(shí)現(xiàn)。Instant Run實(shí)現(xiàn)過程大概分為兩部:
1、構(gòu)造一個(gè)新的AssetManager,并通過反射條用addAssetPath,把這個(gè)完整的新資源包加入到AssetManager中。這樣就得到了一個(gè)含有所有新資源的AssetManager。
2、找到所有之前引用到原油AssetManager的地方,通過反射,把引用處替換為AssetManager
這種方式下發(fā)完整的包很占用空間。而像有些方案,是先進(jìn)行對(duì)資源包做差量,在運(yùn)行時(shí)合成完整包再加載。這樣確實(shí)減少包的體積,但是在運(yùn)行時(shí)多了合成的操作,耗費(fèi)了運(yùn)行時(shí)間喝內(nèi)存。合成后的包也是完整的包,仍舊會(huì)占磁盤空間。
Sophix采用的一種很巧妙的方式,構(gòu)造一個(gè)package id為0x66的資源包,這個(gè)包里面只包含改變了的資源項(xiàng),然后直接在原來的AssetManager中addAssetPaht這個(gè)包。然后,就可以了。憂郁補(bǔ)丁包的package id為0x66和原來文件的package id為0x7f不沖突,因此直接加入到一有的AssetManager中就直接使用了。補(bǔ)丁包里面的資源,只包含原油包里面沒有而新包里有的資源以及內(nèi)容發(fā)生改變的資源。
資源的改變包含增加、減少、修改,分別處理方法如下:
1、新增資源,直接加入布丁包
2、減少的資源,我們只要不使用就行了,因此不用考慮這種情況,它不影響布丁包
3、對(duì)于修改資源,比如替換了一張圖片之類的情況,我們把他視為新增資源,在打入補(bǔ)丁的時(shí)候,代碼在引用出也會(huì)做相應(yīng)的修改,也就是直接把原來使用的舊資源id的地方變?yōu)樾耰d。
Sophix優(yōu)勢(shì):
1.不修改AssetManager的引用處,替換更快更完全。(對(duì)比Instanat Run以及所有copycat的實(shí)現(xiàn))
2.不必下發(fā)完整包,補(bǔ)丁包中只包含有變動(dòng)的資源。(對(duì)比Instanat Run、Amigo等方式的實(shí)現(xiàn))
3.不需要在運(yùn)行時(shí)合成完整包。不占用運(yùn)行時(shí)計(jì)算和內(nèi)存資源。(對(duì)比Tinker的實(shí)現(xiàn))
三、so庫修復(fù)
so庫的修復(fù)本質(zhì)上是對(duì)native方法的修復(fù)和替換。
我們知道JNI編程中,native方法可以通過動(dòng)態(tài)注冊(cè)和靜態(tài)注冊(cè)兩種方式進(jìn)行。動(dòng)態(tài)注冊(cè)的native方法必須實(shí)現(xiàn)`JNI_OnLoad`方法,同時(shí)實(shí)現(xiàn)一個(gè)`JNINativeMethod[]`數(shù)組,靜態(tài)注冊(cè)的native方法必須是`Java+類完整路徑+方法名`的格式。

動(dòng)態(tài)注冊(cè)的native方法映射通過加載so庫過程中調(diào)用JNI_OnLoad方法調(diào)用完成,靜態(tài)注冊(cè)的native方法映射是在該native方法第一次執(zhí)行的時(shí)候才完成映射,當(dāng)然前提是該so庫已經(jīng)load過。
我們采用的是類似類修復(fù)反射注入方式。把補(bǔ)丁so庫的路徑插入到nativeLibraryDirectories數(shù)組的最前面,就能夠達(dá)到加載so庫的時(shí)候是補(bǔ)丁so庫,而不是原來so庫的目錄,從而達(dá)到修復(fù)的目的。

采用這種方案,完全由Sophix在啟動(dòng)期間反射注入patch中的so庫。對(duì)開發(fā)者依然是透明的。不用像某些其他方案需要手動(dòng)替換系統(tǒng)的System.load來實(shí)現(xiàn)替換目的。