正常開發(fā)流程:
新版本上線确封,發(fā)現(xiàn)問題或用戶反饋bug除呵,緊急修復(fù),上線版本隅肥,用戶重新安裝竿奏。
熱修復(fù)流程:
新版本上線,發(fā)現(xiàn)問題或用戶反饋腥放,緊急修復(fù)泛啸,上線補(bǔ)丁,自動(dòng)修復(fù)
Thinker解決思路秃症?
在android5.0之前候址,每個(gè)android應(yīng)用只含有一個(gè)dex文件,dex的方法數(shù)量被限制在了65535之內(nèi)种柑,導(dǎo)致apk引入大量第三方sdk后方法數(shù)量超過限制無法編譯通過岗仑。為了解決這個(gè)問題,Google推出多dex文件的解決方案multidex聚请,一個(gè)apk可以包含多個(gè)dex文件荠雕。通過Multidex.install(this)完成dex文件的加載。
Tinker方案參考multidex實(shí)現(xiàn)原理驶赏,在編譯時(shí)通過新舊兩個(gè)Dex生成差異patch.dex炸卑。在運(yùn)行時(shí),將差異patch.dex重新和原始安裝包的舊Dex合并還原為新的Dex煤傍。這個(gè)過程可能比較耗費(fèi)時(shí)間與內(nèi)存盖文,所以tinker單獨(dú)放在一個(gè)后臺(tái)進(jìn)程:patch中處理。為了補(bǔ)丁包盡量的小蚯姆,微信自研了DexDiff算法五续,它深度利用Dex的格式來減少差異的大小洒敏。由于采用ClassLoader機(jī)制,所以需要app重啟疙驾。
Tinker基于基版本利用自研的DexDiff算法生成差分包凶伙,放在后臺(tái)。App啟動(dòng)時(shí)下載差分包它碎,與原dex重新合并镊靴,解壓縮,并參考MultiDex.install()流程链韭,重新安裝app。
由于類加載實(shí)現(xiàn)原理涉及dex文件的重新解壓縮合并等處理煮落,消耗內(nèi)存大敞峭,耗時(shí)長,在系統(tǒng)低內(nèi)存時(shí)容易導(dǎo)致熱更新失敗蝉仇,騰訊測(cè)試成功率大概為95%旋讹。實(shí)際熱部署時(shí),差分包應(yīng)文件大小最小轿衔。
綜上沒有完美的熱更新方案沉迹,沒有100%的熱更新成功率。目前害驹,騰訊tinker基本可以滿足app的熱更新需求鞭呕,但隨著app用戶規(guī)模不斷增大,業(yè)務(wù)需求日益復(fù)雜宛官,可考慮阿里的sophix商業(yè)方案葫松,sophix同時(shí)應(yīng)用類加載和底層替換兩種方案,具有底層替換的修改及時(shí)性底洗,和類加載方案的兼容性等優(yōu)點(diǎn)腋么。而且sophix采用非侵入打包,圖形化的生成補(bǔ)丁亥揖,用阿里的原話說就是“傻瓜式接入”珊擂,點(diǎn)一點(diǎn)鼠標(biāo)就能生成補(bǔ)丁文件,而且阿里提供了后臺(tái)補(bǔ)丁管理系統(tǒng)费变,幫助開發(fā)者在生成補(bǔ)丁后直接上傳至阿里的后臺(tái)摧扇,無需開發(fā)者在自己的app和服務(wù)端做任何的操作。
“熱部署” – 方法內(nèi)的簡(jiǎn)單修改胡控,無需重啟app和Activity扳剿。 “暖部署” – app無需重啟,但是activity需要重啟昼激,比如資源的修改庇绽。 “冷部署” – app需要重啟锡搜,比如繼承關(guān)系的改變或方法的簽名變化等。?
熱更新究竟是什么瞧掺?
有一些這樣的情況耕餐, 當(dāng)一個(gè)App發(fā)布之后,突然發(fā)現(xiàn)了一個(gè)嚴(yán)重bug需要進(jìn)行緊急修復(fù)辟狈,這時(shí)候公司各方就會(huì)忙得焦頭爛額:重新打包App肠缔、測(cè)試、向各個(gè)應(yīng)用市場(chǎng)和渠道換包哼转、提示用戶升級(jí)明未、用戶下載、覆蓋安裝壹蔓。有時(shí)候僅僅是為了修改了一行代碼趟妥,也要付出巨大的成本進(jìn)行換包和重新發(fā)布。
這種需要替換運(yùn)行時(shí)新的類和資源文件的加載佣蓉,就可以認(rèn)為是熱操作了披摄。而在熱更新出現(xiàn)之前,通過反射注解勇凭、反射調(diào)用和反射注入等方式已經(jīng)可以實(shí)現(xiàn)類的動(dòng)態(tài)加載了疚膊。而熱更新框架的出現(xiàn)就是為了解決這樣一個(gè)問題的。從某種意義上來說虾标,熱更新就是要做一件事寓盗,替換。當(dāng)替換的東西屬于大塊內(nèi)容的時(shí)候夺巩,就是模塊化了贞让,當(dāng)你去替換方法的時(shí)候,叫熱更新柳譬,當(dāng)你替換類的時(shí)候喳张,加熱插件,而且重某種意義上講美澳,所有的熱更新方案销部,都是一種熱插件,因?yàn)闊岣路桨妇褪窃赼pp之外去干這個(gè)事制跟。就這么簡(jiǎn)單的理解舅桩。無論是替換一個(gè)類,還是一個(gè)方法雨膨,都是在干替換這件事請(qǐng)擂涛。。這里的替換聊记,也算是幾種hook操作撒妈,無論在什么代碼等級(jí)上恢暖,都是一種侵入性的操作。
所以總結(jié)一句話簡(jiǎn)單理解熱更新 HotFix 就是改變app運(yùn)行行為的技術(shù)狰右!(或者說就是對(duì)已發(fā)布app進(jìn)行bug修復(fù)的技術(shù))
目前存在的主流框架杰捂?
先后出現(xiàn)了Dexposed、AndFix,Qzone超級(jí)補(bǔ)丁的類Nuwa方式棋蚌,微信的Tinker, 大眾點(diǎn)評(píng)的nuwa嫁佳、百度金融的rocooFix, 餓了么的amigo以及美團(tuán)的robust.以及阿里的Sophix.騰訊的內(nèi)部方案KKFix.
框架橫向?qū)Ρ龋?/h1>
實(shí)現(xiàn)套路?
對(duì)比點(diǎn)評(píng)谷暮?
AndFix作為native解決方案蒿往,首先面臨的是穩(wěn)定性與兼容性問題,更重要的是它無法實(shí)現(xiàn)類替換湿弦,它是需要大量額外的開發(fā)成本的熄浓;
Robust兼容性與成功率較高,但是它與AndFix一樣省撑,無法新增變量與類只能用做的bugFix方案;
Qzone方案可以做到發(fā)布產(chǎn)品功能俯在,但是它主要問題是插樁帶來Dalvik的性能問題竟秫,以及為了解決Art下內(nèi)存地址問題而導(dǎo)致補(bǔ)丁包急速增大的。
特別是在Android N之后跷乐,由于混合編譯的inline策略修改肥败,對(duì)于市面上的各種方案都不太容易解決。而Tinker熱補(bǔ)丁方案不僅支持類愕提、So以及資源的替換馒稍,它還是2.X-8.X(1.9.0以上支持8.X)的全平臺(tái)支持。利用Tinker我們不僅可以用做bugfix,甚至可以替代功能的發(fā)布
類加載機(jī)制浅侨?
我們知道Android系統(tǒng)也是仿照java搞了一個(gè)虛擬機(jī)纽谒,不過它不叫JVM,它叫Dalvik/ART VM他們還是有很大區(qū)別的(這是不是我們的重點(diǎn), 點(diǎn)開是個(gè)拓展閱讀)如输。我們只需要知道鼓黔,Dalvik/ART VM 虛擬機(jī)加載類和資源也是要用到ClassLoader,不過Jvm通過ClassLoader加載的class字節(jié)碼不见,而Dalvik/ART VM通過ClassLoader加載則是dex澳化。
Android的類加載器分為兩種,PathClassLoader和DexClassLoader,兩者都繼承自BaseDexClassLoader
PathClassLoader代碼位于libcore\dalvik\src\main\Java\dalvik\system\PathClassLoader.java?
DexClassLoader代碼位于libcore\dalvik\src\main\java\dalvik\system\DexClassLoader.java?
BaseDexClassLoader代碼位于libcore\dalvik\src\main\java\dalvik\system\BaseDexClassLoader.java
PathClassLoader
用來加載系統(tǒng)類和應(yīng)用類
DexClassLoader
用來加載jar稳吮、apk缎谷、dex文件.加載jar、apk也是最終抽取里面的Dex文件進(jìn)行加載.
歸納一下就是:ClassLoader會(huì)遍歷這個(gè)數(shù)組,然后加載這個(gè)數(shù)組中的dex文件.?
而ClassLoader在加載到正確的類之后,就不會(huì)再去加載有Bug的那個(gè)類了,我們把這個(gè)正確的類放在Dex文件中,讓這個(gè)Dex文件排在dexElements數(shù)組前面即可.
為什么推薦Sophix?
AndFix作為native解決方案蒿往,首先面臨的是穩(wěn)定性與兼容性問題,更重要的是它無法實(shí)現(xiàn)類替換湿弦,它是需要大量額外的開發(fā)成本的熄浓;
Robust兼容性與成功率較高,但是它與AndFix一樣省撑,無法新增變量與類只能用做的bugFix方案;
Qzone方案可以做到發(fā)布產(chǎn)品功能俯在,但是它主要問題是插樁帶來Dalvik的性能問題竟秫,以及為了解決Art下內(nèi)存地址問題而導(dǎo)致補(bǔ)丁包急速增大的。
特別是在Android N之后跷乐,由于混合編譯的inline策略修改肥败,對(duì)于市面上的各種方案都不太容易解決。而Tinker熱補(bǔ)丁方案不僅支持類愕提、So以及資源的替換馒稍,它還是2.X-8.X(1.9.0以上支持8.X)的全平臺(tái)支持。利用Tinker我們不僅可以用做bugfix,甚至可以替代功能的發(fā)布
我們知道Android系統(tǒng)也是仿照java搞了一個(gè)虛擬機(jī)纽谒,不過它不叫JVM,它叫Dalvik/ART VM他們還是有很大區(qū)別的(這是不是我們的重點(diǎn), 點(diǎn)開是個(gè)拓展閱讀)如输。我們只需要知道鼓黔,Dalvik/ART VM 虛擬機(jī)加載類和資源也是要用到ClassLoader,不過Jvm通過ClassLoader加載的class字節(jié)碼不见,而Dalvik/ART VM通過ClassLoader加載則是dex澳化。
Android的類加載器分為兩種,PathClassLoader和DexClassLoader,兩者都繼承自BaseDexClassLoader
PathClassLoader代碼位于libcore\dalvik\src\main\Java\dalvik\system\PathClassLoader.java?
DexClassLoader代碼位于libcore\dalvik\src\main\java\dalvik\system\DexClassLoader.java?
BaseDexClassLoader代碼位于libcore\dalvik\src\main\java\dalvik\system\BaseDexClassLoader.java
PathClassLoader
用來加載系統(tǒng)類和應(yīng)用類
DexClassLoader
用來加載jar稳吮、apk缎谷、dex文件.加載jar、apk也是最終抽取里面的Dex文件進(jìn)行加載.
歸納一下就是:ClassLoader會(huì)遍歷這個(gè)數(shù)組,然后加載這個(gè)數(shù)組中的dex文件.?
而ClassLoader在加載到正確的類之后,就不會(huì)再去加載有Bug的那個(gè)類了,我們把這個(gè)正確的類放在Dex文件中,讓這個(gè)Dex文件排在dexElements數(shù)組前面即可.
為了提升產(chǎn)品在敏捷開發(fā)下的最佳發(fā)布體驗(yàn)灶似,分別嘗試了備受關(guān)注的阿里和微信兩大派系的熱更新方案(支付寶的Andfix和微信的Tinker)列林,但在探索的過程中瑞你,發(fā)現(xiàn)兩種方案都存在弊端,如使用場(chǎng)景有限席纽,修復(fù)成功率低捏悬,存在兼容問題。加之各方案還在內(nèi)部快速迭代润梯,均未能達(dá)到商業(yè)化的標(biāo)準(zhǔn)过牙,所以熱修復(fù)在項(xiàng)目中的應(yīng)用被暫時(shí)擱置了。
? ? ? ?直到最近纺铭,看到阿里推出了非侵入式熱修復(fù)框架Sophix寇钉。Sophix對(duì)其前輩Andfix,阿里百川Hotfix等方案進(jìn)行了升級(jí)改造舶赔,打破了舊方案諸多限制扫倡,涵蓋了代碼修復(fù),資源修復(fù)竟纳,So庫修復(fù)撵溃。加上阿里云平臺(tái)的支持,經(jīng)過簡(jiǎn)單的配置就可接入使用锥累,目之所及缘挑,Sophix已經(jīng)成為目前成熟度最高的熱修復(fù)框架。這也讓我重新燃起了對(duì)熱更新及底層技術(shù)探索的熱情桶略。所以我想以此為契機(jī)语淘,用系列文章的形式,圍繞熱點(diǎn)技術(shù)所涉獵的知識(shí)進(jìn)行由淺入深的持續(xù)挖掘际歼。
修復(fù)立即生效惶翻,是熱修復(fù)所追求的,底層替換方案通過在運(yùn)行時(shí)利用hook操作native指針實(shí)現(xiàn)“熱”的特性鹅心。但這里有一個(gè)關(guān)鍵點(diǎn)吕粗,底層替換所操作的指針,實(shí)際上是ArtMethod旭愧,在類被加載溯泣,類中的每個(gè)方法都會(huì)有對(duì)應(yīng)的ArtMethod,它記錄了方法包括所屬類和內(nèi)存地址信息榕茧,Andfix正是通過篡改ArtMethod垃沦,將補(bǔ)丁方法ArtMethod的成員值逐一賦給舊方法,實(shí)現(xiàn)替換用押。問題就出現(xiàn)在這個(gè)逐一上肢簿。因?yàn)锳ndfix的ArtMethod方法結(jié)構(gòu)是根據(jù)Android開源代碼寫死的,面對(duì)國內(nèi)廠商的定制,經(jīng)常會(huì)導(dǎo)致兩者ArtMethod方法結(jié)構(gòu)不一致池充,這也是兼容問題產(chǎn)生的根本原因桩引。
? ? ? ?為了解決這個(gè)問題,Sophix采用了對(duì)舊ArtMethod進(jìn)行完整替換收夸,通過動(dòng)態(tài)測(cè)量ArtMethod的size(通過c層的mempy(dest ,src ,size)方法)坑匠,進(jìn)行全量拷貝。這樣做無論ArtMethod被修改成什么樣卧惜,只需要統(tǒng)一執(zhí)行拷貝厘灼,就可以完成替換,完全無視修改虛擬機(jī)導(dǎo)致的ArtMethod結(jié)構(gòu)差異.
?底層替換雖能使修復(fù)即時(shí)生效咽瓷,但由于類加載后设凹,方法結(jié)構(gòu)已固定,這就造成使用上會(huì)有諸多限制茅姜。相反類加載方案的使用場(chǎng)景更為廣泛闪朱。Sophix使用類加載作為兜底方案。在熱部署無法使用的情況下钻洒,自動(dòng)降級(jí)為冷部署方案奋姿。
? ? ? ?無論是冷部署還是熱部署,都需要通過同一套補(bǔ)丁兼顧素标,在Art虛擬機(jī)下胀蛮,默認(rèn)支持多dex加載,虛擬機(jī)會(huì)優(yōu)先加載命名為classes.dex的文件糯钙。Sophix利用了這一點(diǎn),將補(bǔ)丁文件命名為classes.dex退腥,并對(duì)原有dex文件進(jìn)行排序任岸。這樣一來,art虛擬機(jī)就會(huì)先加載補(bǔ)丁文件狡刘,后續(xù)加載的同類名的類會(huì)被忽略享潜,最后將加載得到的dexFile把dexElements整體替換。
? ? ? Dalvik虛擬機(jī)下情況則有些不同嗅蔬,Dalvik默認(rèn)只加載classes.dex剑按,其他dex則被忽略。那么澜术,Sophix就需要一個(gè)全量dex艺蝴,前面提到tinker采用自主研發(fā)的dexDiff技術(shù),從方法和指令的維度進(jìn)行dex合成鸟废,但Dex合成過程發(fā)生在虛擬機(jī)堆內(nèi)存上猜敢,修復(fù)的成功率極大的受到性能問題的影響。為了解決這個(gè)問題,Sophix換了一種思路缩擂,從類的維度鼠冕,對(duì)照補(bǔ)丁包中出現(xiàn)的類,在原有包中做刪除操作胯盯,如圖懈费。
? ?為了避免刪除整個(gè)類信息而導(dǎo)致dex結(jié)構(gòu)發(fā)生偏移,所以只對(duì)舊包中類的入口進(jìn)行刪除博脑,實(shí)際上類的信息還在dex包中憎乙。這樣一來,冷啟動(dòng)后趋厉,原有的類就不會(huì)被加載寨闹,相比Tinker的合成方案,Sophix的思路更為輕量化君账。
? ? ? ?至此繁堡,對(duì)Sophix對(duì)類文件修復(fù)的基本原理描述完畢∠缡可以說Sophix吸取了百家之長椭蹄,對(duì)問題的解決之法堪稱巧妙。但在驚嘆阿里技術(shù)底蘊(yùn)的同時(shí)净赴,也展現(xiàn)出底層技術(shù)的重要性绳矩,若沒有對(duì)虛擬機(jī)等底層技術(shù)的深耕探索,在系統(tǒng)框架的紛繁規(guī)則面前玖翅,也只能至于庭前止步翼馆。