一闸度、概述
??以前對Android 的熱修復(fù)方案有一些了解侣肄,知道幾個(gè)有名的開源方案,原理大概理解印屁,但是沒有整理匯總一下循捺,上周聽了玉斌大哥在公司做的分享后,感受頗多覺得寫篇博客記一下雄人,不能浪費(fèi)从橘。
??熱修復(fù)是指在不發(fā)新版的情況下修復(fù)線上的緊急 bug,長久以來做移動(dòng)開發(fā)的人員都羨慕做后端或者做 web 前端的人員可以隨時(shí)發(fā)布來修復(fù) bug。那么 移動(dòng)開發(fā)有沒有這樣的方案呢恰力?
Hybrid
如:PhoneGap叉谜、國內(nèi)的 AppCan。本質(zhì)還是利用了 Web 開發(fā)在更新上的優(yōu)勢牺勾,但體驗(yàn)不如 Native正罢。
ReactNative
像Web一樣可以隨時(shí)發(fā)布,又有原生的體驗(yàn)驻民,所以這個(gè)方案優(yōu)勢挺大翻具,有很多項(xiàng)目都在嘗試。
Native HotFix
可以不用發(fā)新版本回还,直接下發(fā)補(bǔ)丁包修復(fù) bug裆泳,純原生體驗(yàn)。
iOS有JSPatch柠硕、Android 有 AndFix工禾、Nuwa、DroidFix 蝗柔、HotFix等
二闻葵、Android Native HotFix
下面按照原理分類為
(一) Dex分包方案(QZone的方案)(如 Nuwa、DroidFix癣丧、HotFix槽畔、RocooFix)
??原理:如果多個(gè) dex 里有相同的class,那么虛擬機(jī)會(huì)優(yōu)先使用最先加載的 dex 中的 class胁编。所以將有問題的類打包到一個(gè)新dex(pathc.dex)厢钧,然后下發(fā)給 app,app 啟動(dòng)時(shí)將該patch.dex插入到其他 dex 前面優(yōu)先加載嬉橙。就達(dá)到了 替換有問題 class 的目的早直。
這里有兩個(gè)難點(diǎn):
1. 改變dex加載順序
2. class_ispreverified
錯(cuò)誤信息:java.lang.IllegalAccessError: Class ref in pre-verified class resolved to unexpected implementation
原因
??dex轉(zhuǎn)化成odex時(shí)會(huì)執(zhí)行dvmVerifyClass進(jìn)行類的校驗(yàn),如 B的引用和B都在一個(gè) dex 里市框,則 B 會(huì)被打上class_ispreverified標(biāo)記霞扬,但是我們優(yōu)先加載 A’,A’和B在兩個(gè)不同的 dex,但 B 卻打上了class_ispreverified標(biāo)識(shí)所以就報(bào)錯(cuò)了。
??兩個(gè)相關(guān)聯(lián)的類在不同的dex中并且有class_ispreverified就會(huì)報(bào)錯(cuò)枫振,谷歌MultiDex拆分dex的時(shí)候就會(huì)處理這種情況如果A 和 B 不在同一個(gè) Dex 就不會(huì)給 B標(biāo)記class_ispreverified喻圃。
??但我們是熱修復(fù),patch.dex 是事后打的 dex,class.dex已經(jīng)安裝的用戶手機(jī)里了蒋得,所以我們無法修改 B 的標(biāo)識(shí)级及。
解決方案:
我們發(fā)布版本打包時(shí)乒疏,將所有的類都引用一個(gè)單獨(dú)dex的一個(gè)類额衙,這個(gè)所有的類都不會(huì)標(biāo)記class_ispreverified了,當(dāng)然這也就失去了class_ispreverified的意義,會(huì)有性能上的影響窍侧。
更新:還有一個(gè)巧妙的方案來防止class_ispreverified,就是在工程里放入一個(gè)android.jar 已經(jīng)存在的類县踢,這樣會(huì)導(dǎo)致multiple class declarations 從而不會(huì)再對class 校驗(yàn)。比如使用com.android.internal.util.Predicate伟件,這樣類的特點(diǎn)是特別簡單沒有額外引用硼啤,然后它從android api 8 就存在了。
QZone 安卓App熱補(bǔ)丁動(dòng)態(tài)修復(fù)技術(shù)介紹
深度理解Android InstantRun原理以及源碼分析
(二)smali diff方案(阿里的方案)(以 AndFix為例)
1. 原理
2. 方法替換(AOP)
??搞過 Android Aop 的同學(xué)知道斧账,我們可以在執(zhí)行一個(gè)方法的前插入另一個(gè)方法谴返,運(yùn)用這個(gè)思路,我們可以把有 bug 的方法替換成我們下發(fā)的新方法咧织。
下邊是 AndFix 使用 JNI hook 方法的一段代碼嗓袱,供大家理解。另一個(gè)實(shí)現(xiàn)AOP 的開源方案
dexposed
Android AOP 常用有的方法有 JNI HOOK 和 靜態(tài)織入习绢。
常用的靜態(tài)織入的庫有:
1渠抹、APT 是代碼生成,java文件 生成 java文件闪萄。使用的annotation類型是SOURCE.
2梧却、Aspectj是靜態(tài)織入。靜態(tài)織入:指在編譯時(shí)期就織入败去,即:編譯出來的class文件放航,字節(jié)碼就已經(jīng)被織入了。AspectJ就是一個(gè)編譯器为迈,把java文件編譯成想要的class文件三椿。使用的annotation類型是SOURCE.
3、ASM或者Javassist或者dexmaker 是Java 字節(jié)碼操控框架葫辐。它能被用來動(dòng)態(tài)生成類或者增強(qiáng)既有類的功能搜锰。使用的annotation類型是class.
注:
cglib底層采用ASM字節(jié)碼生成框架,實(shí)現(xiàn)動(dòng)態(tài)代理實(shí)現(xiàn)AOP
cglib在android中是不能使用的耿战。使用dexmaker框架來仿照動(dòng)態(tài)生成.dex文件蛋叼,實(shí)現(xiàn)cglib的動(dòng)態(tài)代理功能
3. ApkPatch tool
??AndFix 提供了一個(gè)打包 patch 的工具,這個(gè)工具將舊Apk和新Apk做 diff 得出一個(gè)patch apk剂陡。
熟悉增量更新的同學(xué)應(yīng)該知道狈涮,增量更新也是做 diff 求增量包,但是他是在字節(jié)碼層面上求增量包鸭栖,增量包+舊 apk 合并成新 apk歌馍。
??而ApkPatch是通過對比兩個(gè) apk 反編譯后的 smali 文本文件來求 diff 的。所以他是從 class 層面上求 diff晕鹊。得到的patch就是標(biāo)準(zhǔn)的 dex 包松却。 該 dex 可直接被 load暴浦。
4. 以前注意事項(xiàng)
一個(gè) patch 包要修復(fù)哪些類的哪些方法,這個(gè)路徑信息會(huì)被打包到 patch.apk 的META-INFO里
patch.apk在打包時(shí)要使用相同的簽名晓锻。
混淆的情況:打包 patch.apk 時(shí)要使用主apk混淆時(shí)導(dǎo)出的 mapping 文件歌焦,防止主apk和patch.apk 由于混淆而無法對應(yīng)相應(yīng)的類和函數(shù)。
目前 apkpatch 沒有開源砚哆,還有一些小 bug独撇,如不支持 mulitdex。雖然沒有開源但還是有方法修復(fù)的躁锁,比如反編譯導(dǎo)出源碼纷铣、還有 javasist 神器。具體怎么修復(fù)以后再說战转,也可能官方會(huì)修復(fù)关炼。
(三)微信的方案
AndFix 由于影響正向開發(fā)過程(只能修改方法、不能修改 field匣吊、不能新增類等問題)儒拂、庫本身難于維護(hù)以及發(fā)現(xiàn)的莫名其妙的 bug(不同rom對dex的校驗(yàn)機(jī)制不一致)。
nuwa 僅支持更新 Java 代碼色鸳,不能更新資源和 so 文件社痛。
全量替換新的Dex。即我們完全使用了新的Dex命雀,那樣既不出現(xiàn)Art地址錯(cuò)亂的問題蒜哀,在Dalvik也無須插樁。當(dāng)然考慮到補(bǔ)丁包的體積吏砂,我們不能直接將新的Dex放在里面撵儿。但我們可以將新舊兩個(gè)Dex的差異放到補(bǔ)丁包中,最簡單我們可以采用BsDiff算法狐血。
附《Android核心知識(shí)筆記2020》分享
前段時(shí)間我和圈子里的幾位架構(gòu)師朋友一起閑聊時(shí)的突發(fā)奇想淀歇,我們在學(xué)習(xí)Android開發(fā)的時(shí)候或多或少也受到了一些前輩的指導(dǎo),所以想把這份情懷延續(xù)下去匈织。三個(gè)月后浪默,這套資料就出來了,需要這份資料的朋友加Android學(xué)習(xí)交流群1049273031即可獲取缀匕。