目前Android業(yè)內(nèi)刀闷,熱修復(fù)技術(shù)百花齊放,各大廠都推出了自己的熱修復(fù)方案嘿架,使用的技術(shù)方案也各有所異,當(dāng)然各個方案也都存在各自的局限性啸箫。在面對眾多的方案耸彪,希望通過梳理這些熱修復(fù)方案的對比及實現(xiàn)原理,掌握熱修復(fù)技術(shù)的本質(zhì)忘苛,同時也對項目接入做好準(zhǔn)備蝉娜。
簡單來說扎唾,就是通過下發(fā)補(bǔ)丁包召川,讓已安裝的客戶端動態(tài)更新,讓用戶可以不用重新安裝APP胸遇,就能夠修復(fù)軟件缺陷的一種技術(shù)荧呐。
隨著熱修復(fù)技術(shù)的發(fā)展,不僅可以修復(fù)代碼,同時可以修復(fù)資源文件及SO庫倍阐。
目前最快捷的集成方式是 集成 bugly 升級sdk。
需要的工作
代理 Application
接入 tinker-support 插件
編寫tinker gradle 腳本
每次打包需要修改tinker gradle 腳本的配置
測試下來發(fā)現(xiàn)激活成功率 大概 60%
主流配置的機(jī)器峰搪,平均合成時間 1800秒
基礎(chǔ)版 免費(fèi):最大補(bǔ)丁大小:500k 日請求量 <1萬
專業(yè)版 399 - 2899元/月
補(bǔ)丁即時生效岔冀,不需要應(yīng)用重啟;
補(bǔ)丁包同樣采用差量技術(shù)概耻,生成的PATCH體積惺固住;
對應(yīng)用無侵入鞠柄,幾乎無性能損耗侦高;
兩行代碼,傻瓜式接入厌杜。
免費(fèi)閾值:月活設(shè)備(MAU): 5萬矫膨。
每個月,每臺設(shè)備收費(fèi)0.015元期奔。
計費(fèi)周期:系統(tǒng)每日生成賬單,進(jìn)行結(jié)算危尿。當(dāng)月收取過的呐萌,不再進(jìn)行收費(fèi)。即只計算日增量設(shè)備
java hook 插樁
無差別兼容Android2.3-8.0版本谊娇;
無需重啟補(bǔ)丁實時生效肺孤,
補(bǔ)丁修補(bǔ)成功率高達(dá)99.9%(所有熱修復(fù)方案中成功率最高的)
只支持方法級別的Bug修復(fù),不支持資源及so
bug修復(fù)通過java hook 代碼實現(xiàn)
補(bǔ)丁的下發(fā)和合并等需要自己實現(xiàn)
如果考慮付費(fèi)济欢,推薦選擇阿里的Sophix赠堵,Sophix是綜合優(yōu)化的產(chǎn)物,功能完善法褥、開發(fā)簡單透明茫叭、提供分發(fā)及監(jiān)控管理。
如果不考慮付費(fèi)半等,只需支持方法級別的Bug修復(fù)揍愁,不支持資源及so,推薦使用Robust杀饵。補(bǔ)丁修補(bǔ)成功率高達(dá)99.9%(所有熱修復(fù)方案中成功率最高的)
如果考慮需要同時支持資源及so莽囤,使用Tinker。不建議使用切距,因為實現(xiàn)原理是 dex合成后替換朽缎,dex合成成功率不高(60%)
如果公司綜合實力強(qiáng),可考慮自研,靈活性及可控制最強(qiáng)话肖。
從Github上的熱度及提交記錄上看北秽,nuwa、AndFix狼牺、Amigo等的提交都是2 years ago羡儿。
內(nèi)業(yè)主要熱修復(fù)技術(shù)方案原理
底層替換和類加載(dex插入/替換)
類加載有兩種實現(xiàn):dexElements和替換dex;所以又稱三大流派
美團(tuán) Robust 這種 java方法插樁hook的是钥,只能實現(xiàn)代碼修復(fù)掠归,無法實現(xiàn)資源和so修復(fù);所以不在常規(guī)討論范圍內(nèi)悄泥。
代表:阿里系的 Andfix HotFix
通過Andfix提供的工具對比出新舊apk 的?classes.dex?文件的差異虏冻,并生成patch壓縮包(jar包)
壓縮包中比較關(guān)鍵的是?PATCH.MF?(補(bǔ)丁類名)和?diff.dex?(補(bǔ)丁方法)
虛擬機(jī)通過 jar包 讀取 補(bǔ)丁類名和補(bǔ)丁方法
通過classLoader,找到要修復(fù)的bug類名及方法
利用hook技術(shù)弹囚,在native修改指ArtMethod針變量厨相,使其指向補(bǔ)丁方法,從而完成bug修復(fù)鸥鹉。
在類加載后蛮穿,動態(tài)修改native指針,修復(fù)即時生效毁渗,無需冷啟動
類已經(jīng)被加載践磅,內(nèi)存中方法描述符(結(jié)構(gòu)體)已經(jīng)固定,所以只能替換灸异,不能做新增修復(fù)府适。
在Native操作指針時,強(qiáng)轉(zhuǎn)ArtMethod的類型是AndFix寫死的肺樟,無法保證是運(yùn)行時的ArtMethod結(jié)構(gòu)檐春,這會產(chǎn)生十分嚴(yán)重的兼容問題
實踐發(fā)現(xiàn)Andfix?修復(fù)成功率非常低?,時常出現(xiàn)崩潰么伯,補(bǔ)丁無效的現(xiàn)象
代表:騰訊系的 Qzone超級補(bǔ)丁(dex插入) Tinker(dex替換)
增量Dex
Hook ClassLoader.pathList.dexElements[]
將補(bǔ)丁的dex插入到數(shù)組的最前端疟暖。
ClassLoader的findClass是通過遍歷dexElements[]中的dex來尋找類的。所以會優(yōu)先查找到修復(fù)的類田柔。從而達(dá)到修復(fù)的效果誓篱。
Vm的判定規(guī)則:“當(dāng)一個類中引用了另外一個類,則一般要求兩個類來自同一個Dex文件”凯楔。
CLASS_ISPREVERIFIED?是觸發(fā)Vm判定規(guī)則的前提窜骄。
增量方案為解決這個問題,需要進(jìn)行“打樁”摆屯。
打樁的目的就是防止類被打上?CLASS_ISPREVERIFIED?標(biāo)簽邻遏。
打樁糠亩,就是在所有類中分別引用另外一個獨(dú)立Dex文件(為了打樁特意封裝的)中的類。通常做法是在每一個類中增加構(gòu)造器并引用另外一個dex中的類准验。
在類加載的最后階段赎线,虛擬機(jī)會對未打上?CLASS_ISPREVERIFIED?標(biāo)簽的類 再次進(jìn)行?校驗和優(yōu)化?,如果在同一時間點加載大量類糊饱,那么就會出現(xiàn)嚴(yán)重的性能問題垂寥,如啟動時白屏。
不需要考慮對dalvik虛擬機(jī)和art虛擬機(jī)做適配
代碼是非侵入式的另锋,對apk體積影響不大
需要下次啟動才修復(fù)
性能損耗大滞项,為了避免類被加上?CLASS_ISPREVERIFIED,使用插樁夭坪,單獨(dú)放一個幫助類在獨(dú)立的dex中讓其他類調(diào)用文判。可能導(dǎo)致嚴(yán)重的性能問題室梅,如啟動時白屏戏仓。
全量Dex替換
為了避免dex插樁帶來的性能損耗,dex替換采取另外的方式(整體替換dex)亡鼠。
提供dex差量包(只包含patch代碼的dex)
將patch.dex與應(yīng)用的classes.dex合并成一個完整的dex
加載完整dex得到dexFile對象作為參數(shù)構(gòu)建一個Element對象
整體替換掉舊的dex-Elements數(shù)組
相比 dex插入赏殃,dex替換的優(yōu)點
減少了dex插樁帶來的性能損耗
Dex合并內(nèi)存消耗在虛擬機(jī)堆內(nèi)存(vm heap)上,容易OOM间涵,最后導(dǎo)致合并失敗
底層替換存在不同定制Rom的兼容性問題仁热,同時不能做新增field的修復(fù),但修復(fù)立即生效浑厚。
類加載方案在合成全量補(bǔ)丁的時候存在性能問題,修復(fù)需要重啟應(yīng)用(冷啟動)根盒,但是兼容性較好钳幅。
Sophix對類文件修復(fù) 采用底層替換方案為主,類加載方案為次(兜底策略)的模式炎滞,將二者結(jié)合起來敢艰,并對二者另辟蹊徑,加以突破册赛。
底層替換方案通過在運(yùn)行時利用hook操作native指針實現(xiàn)“熱”的特性钠导。但這里有一個關(guān)鍵點,底層替換所操作的指針森瘪,實際上是?ArtMethod?牡属。
在類被加載,類中的每個方法都會有對應(yīng)的ArtMethod扼睬,它記錄了方法包括所屬類和內(nèi)存地址信息
Andfix正是通過篡改ArtMethod逮栅,將補(bǔ)丁方法ArtMethod的成員值逐一賦給舊方法,實現(xiàn)替換。
問題就出現(xiàn)在?逐一替換?上措伐。因為Andfix的?ArtMethod?方法結(jié)構(gòu)是根據(jù)Android開源代碼寫死的特纤,面對國內(nèi)廠商的定制,經(jīng)常會導(dǎo)致兩者ArtMethod方法結(jié)構(gòu)不一致侥加,這也是兼容問題產(chǎn)生的根本原因捧存。
為了解決這個問題,Sophix采用了對舊ArtMethod進(jìn)行?完整替換担败。
通過動態(tài)測量ArtMethod的size(通過c層的mempy(dest ,src ,size)方法)昔穴,進(jìn)行全量拷貝。這樣做無論ArtMethod被修改成什么樣氢架,只需要統(tǒng)一執(zhí)行拷貝傻咖,就可以完成替換,完全無視修改虛擬機(jī)導(dǎo)致的ArtMethod結(jié)構(gòu)差異岖研。
底層替換雖能使修復(fù)即時生效卿操,但由于類加載后,方法結(jié)構(gòu)已固定孙援,這就造成使用上會有諸多限制害淤。
相反類加載方案的使用場景更為廣泛。
Sophix使用類加載作為兜底方案拓售。在熱部署無法使用的情況下窥摄,自動降級為冷部署方案。
無論是冷部署還是熱部署础淤,都需要通過同一套補(bǔ)丁兼顧崭放。
在Art虛擬機(jī)下,默認(rèn)支持多dex加載鸽凶,虛擬機(jī)會優(yōu)先加載命名為classes.dex的文件币砂。
Sophix利用了這一點,將補(bǔ)丁文件命名為classes.dex玻侥,并對原有dex文件進(jìn)行排序决摧。這樣一來,art虛擬機(jī)就會先加載補(bǔ)丁文件凑兰,后續(xù)加載的同類名的類會被忽略掌桩,最后將加載得到的dexFile把dexElements整體替換。
Dalvik默認(rèn)只加載classes.dex姑食,其他dex則被忽略波岛。
Sophix就需要一個全量dex。
tinker采用自主研發(fā)的dexDiff技術(shù)音半,從方法和指令的維度進(jìn)行dex合成盆色,但Dex合成過程發(fā)生在虛擬機(jī)堆內(nèi)存上灰蛙,修復(fù)的成功率極大的受到性能問題的影響。
為了解決這個問題隔躲,Sophix換了一種思路摩梧,從類的維度,對照補(bǔ)丁包中出現(xiàn)的類宣旱,在原有包中做刪除操作仅父。為了避免刪除整個類信息而導(dǎo)致dex結(jié)構(gòu)發(fā)生偏移,所以只對舊包中類的入口進(jìn)行刪除浑吟,實際上類的信息還在dex包中笙纤。這樣一來嘱朽,冷啟動后锣杂,原有的類就不會被加載叙身,相比Tinker的合成方案及汉,Sophix的思路更為輕量化。
至此膘螟,Sophix對類文件修復(fù)的基本原理描述完畢虽惭。
可以說Sophix吸取了百家之長敌蚜,對問題的解決之法堪稱巧妙候衍,展現(xiàn)出底層技術(shù)的重要性笼蛛,若沒有對虛擬機(jī)等底層技術(shù)的深耕探索,在系統(tǒng)框架的紛繁規(guī)則面前蛉鹿,也只能至于庭前止步滨砍。
Android熱修復(fù)技術(shù),你會怎么選妖异?