By吳思博
一惋嚎、什么是熱修復(fù):
二、技術(shù)背景
三些楣、當(dāng)前主流的熱修復(fù)方案比較
四、Andfix的介紹與實(shí)踐(開(kāi)源)
1原理
2基本使用
五宪睹、其他方案介紹
一愁茁、什么是熱修復(fù):
熱修復(fù)(HotFix)是以補(bǔ)丁的方式動(dòng)態(tài)修復(fù)緊急Bug,不再需要重新發(fā)布App亭病,不需要用戶重新下載覆蓋安裝的方式來(lái)實(shí)現(xiàn)代碼的替換修改鹅很。
二、技術(shù)背景
(1)正常開(kāi)發(fā)流程
從流程來(lái)看罪帖,傳統(tǒng)的開(kāi)發(fā)流程存在很多弊端:
1促煮、重新發(fā)布版本代價(jià)太大
2、用戶下載安裝成本太高
3整袁、BUG修復(fù)不及時(shí)菠齿,用戶體驗(yàn)太差
(2)熱修復(fù)開(kāi)發(fā)流程
而熱修復(fù)的開(kāi)發(fā)流程顯得更加靈活,優(yōu)勢(shì)很多:
1坐昙、無(wú)需重新發(fā)版绳匀,實(shí)時(shí)高效修復(fù)
2、用戶無(wú)感知修復(fù)民珍,無(wú)需下載新的應(yīng)用襟士,代價(jià)小
3、修復(fù)成功率高嚷量,把損失降到最低
三陋桂、當(dāng)前主流的熱修復(fù)方案比較:
熱修復(fù)技術(shù)近期變得越來(lái)越熱門(mén),同時(shí)也出現(xiàn)了一些不同的解決方案蝶溶,如阿里的AndFix(開(kāi)源)嗜历、QQ空間補(bǔ)丁方案、以及微信的Tinker方案抖所,但是它們的原理梨州、適用場(chǎng)景都各有不同。我們項(xiàng)目中可以采用哪種方案田轧,是我們比較關(guān)注的問(wèn)題暴匠。
QQ空間超級(jí)補(bǔ)丁技術(shù)和微信Tinker支持新增類(lèi)和資源的替換,在一些功能化的更新上更為強(qiáng)大傻粘,但對(duì)應(yīng)用的性能和穩(wěn)定會(huì)有的一定的影響每窖;AndFix雖然暫時(shí)不支持新增類(lèi)和資源的替換帮掉,對(duì)新功能的發(fā)布也有所限制,作為定位為線上緊急BUG的熱修復(fù)的服務(wù)來(lái)說(shuō)窒典,還是比較好的蟆炊,同時(shí)對(duì)應(yīng)用性能不產(chǎn)生不必要的損耗,在熱修復(fù)方面不失為一個(gè)好的選擇瀑志。
四涩搓、Andfix的介紹與實(shí)踐(定位:一個(gè)低成本快速接入的熱修復(fù)第一方案)
Github:https://github.com/alibaba/AndFix
(1)AndFix是一個(gè)Android App的在線熱補(bǔ)丁框架。使用此框架劈猪,我們能夠在不重復(fù)發(fā)版的情況下昧甘,在線修改App中的Bug。AndFix就是“android Hot-Fix”的縮寫(xiě)岸霹。就目前來(lái)說(shuō)疾层,AndFix支持Android 2.3到7.0版本,并且支持arm與X86系統(tǒng)架構(gòu)的設(shè)備贡避。支持Dalvik與ART的Runtime痛黎。AndFix的補(bǔ)丁文件是以.apatch結(jié)尾的文件。
AndFix不同于QQ空間超級(jí)補(bǔ)丁技術(shù)和微信Tinker通過(guò)增加或替換整個(gè)DEX的方案刮吧,提供了一種運(yùn)行時(shí)在Native修改Filed指針的方式湖饱,實(shí)現(xiàn)方法的替換,達(dá)到即時(shí)生效無(wú)需重啟杀捻,對(duì)應(yīng)用無(wú)性能消耗
AndFix實(shí)現(xiàn)過(guò)程:
對(duì)于實(shí)現(xiàn)方法的替換井厌,在Native層操作,經(jīng)過(guò)三個(gè)步驟:
下面以Dalvik設(shè)備為例致讥,來(lái)分析具體的實(shí)現(xiàn)過(guò)程
對(duì)于Dalvik來(lái)說(shuō)仅仆,遵循JIT即時(shí)編譯機(jī)制,需要在運(yùn)行時(shí)裝載libdvm.so動(dòng)態(tài)庫(kù)垢袱,獲取以下內(nèi)部函數(shù):
1 dvmThreadSelf( ):查詢當(dāng)前的線程墓拜;
2 dvmDecodeIndirectRef():根據(jù)當(dāng)前線程獲得ClassObject對(duì)象。
setFieldFlag
動(dòng)態(tài)庫(kù)會(huì)忽略非public屬性的字段和方法请契,該操作的目的:讓private咳榜、protected的方法和字段可被動(dòng)態(tài)庫(kù)看見(jiàn)并識(shí)別。
replaceMethod:
AndFix對(duì)ART設(shè)備同樣支持爽锥,具體的過(guò)程與Dalvik相似涌韩。
該步驟是方法替換的核心,替換的流程如下:
優(yōu)點(diǎn):
1氯夷、BUG修復(fù)的即時(shí)
2臣樱、補(bǔ)丁包同樣采用差量技術(shù),生成的PATCH體積小
3、對(duì)應(yīng)用無(wú)侵入雇毫,幾乎無(wú)性能損耗
不足:
1奢啥、不支持新增字段,以及修改方法嘴拢,也不支持對(duì)資源的替換。
2寂纪、由于廠商的自定義ROM席吴,對(duì)少數(shù)機(jī)型可能暫時(shí)不支持。
(2)Andfix的基本使用捞蛋。
1.在自定義Application中初始化孝冒,為了更早的修復(fù)應(yīng)用中的bug。
package com.euler.andfix;
import android.app.Application;
import com.alipay.euler.andfix.patch.PatchManager;
public class MainApplication extends Application {
public PatchManager mPatchManager;
@Override
public void onCreate() {
super.onCreate();
//初始化patch管理類(lèi)
mPatchManager = new PatchManager(this);
//初始化patch版本
mPatchManager.init("1.0");
//加載已經(jīng)添加到PatchManager中的patch
mPatchManager.loadPatch();
}
}
2.如果有新的補(bǔ)丁需要修復(fù)拟杉,下載完成后庄涡,進(jìn)行以下操作
//添加patch,只需指定patch的路徑即可搬设,補(bǔ)丁會(huì)立即生效
mPatchManager.addPatch(path);
3.當(dāng)apk版本升級(jí)穴店,需要把之前patch文件的刪除,需要以下操作
//刪除所有已加載的patch文件
mPatchManager.removeAllPatch();
我們也可封裝成一個(gè)工具類(lèi)
patch文件的生成
使用工具:apkpatch-1.0.3
原理:根據(jù)兩個(gè)apk包拿穴,生成一個(gè)差異文件泣洞,就是所謂的補(bǔ)丁文件即patch文件。
命令: apkpatch.bat -f new.apk -t old.apk -o output1 -k debug.keystore -p android -a androiddebugkey -e android
-f :新版本
-t :舊版本
-o :輸出目錄
-k :打包所用的keystore
-p :keystore的密碼
-a :keystore用戶別名
-e :keystore用戶別名密碼
執(zhí)行完命令默色,就會(huì)在輸出目錄中輸出.apatch文件:
new-c293df7dbc23f11214fdd020ea78d3b8.apatch
:就是patch文件球凰。
.apatch文件根目錄內(nèi)容:
META_INF
文件下內(nèi)容:
PATCH.MF
文件內(nèi)容:
注:Patch-Classes就是改動(dòng)過(guò)的class.
客戶端請(qǐng)求服務(wù)器接口(api),服務(wù)器根據(jù)用戶傳遞的數(shù)據(jù)分析是否有需要修復(fù)的bug腿宰。如果有bug需要修復(fù)呕诉,就下載服務(wù)器指定的.apatch文件的鏈接,下載完后及時(shí)加載并修復(fù)吃度,使用addpatch(path)方法甩挫,補(bǔ)丁會(huì)立即生效。
dependencies {
compile 'com.alipay.euler:andfix:0.3.1@aar'
}
-keep class * extends java.lang.annotation.Annotation
-keepclasseswithmembernames class * {
native ;
}
Q & A:
1规肴、如何解決某些機(jī)型不兼容的問(wèn)題捶闸?還有采用YunOS系統(tǒng)的?之前測(cè)試在ART模式機(jī)型上拖刃,偶現(xiàn)崩潰删壮,不知采用了什么機(jī)制處理的?線上采用Andfix兑牡,會(huì)不會(huì)風(fēng)險(xiǎn)比較高央碟?
ART的機(jī)型上暫時(shí)未出現(xiàn)崩潰的現(xiàn)象。對(duì)于機(jī)型的兼容性問(wèn)題,采取的措施是做平臺(tái)區(qū)間控制基本兼容亿虽,對(duì)于個(gè)別機(jī)型的問(wèn)題菱涤,可以參考統(tǒng)計(jì)數(shù)據(jù)加入黑名單,與服務(wù)器約定協(xié)議洛勉,黑名單上的機(jī)型不走andifx流程粘秆。美聊線上實(shí)踐暫時(shí)未出現(xiàn)不可控的問(wèn)題
2、沒(méi)有成功更新的情況下收毫,會(huì)不會(huì)引起app的崩潰攻走?
更新不成功不會(huì)引起崩潰
五、其他技術(shù)簡(jiǎn)介
android的類(lèi)加載器分為兩種,PathClassLoader和DexClassLoader此再,兩者都繼承自BaseDexClassLoader
PathClassLoader用來(lái)加載系統(tǒng)類(lèi)和應(yīng)用類(lèi)昔搂。DexClassLoader用來(lái)加載jar、apk输拇、dex文件.加載jar摘符、apk也是最終抽取里面的Dex文件進(jìn)行加載。
1策吠、QQ空間超級(jí)補(bǔ)丁技術(shù)
QQ空間超級(jí)補(bǔ)丁技術(shù)基于DEX分包方案逛裤,使用了多DEX加載的原理,大致的過(guò)程就是:把BUG方法修復(fù)以后奴曙,放到一個(gè)單獨(dú)的DEX里别凹,插入到dexElements數(shù)組的最前面,讓虛擬機(jī)去加載修復(fù)完后的方法洽糟。
當(dāng)patch.dex中包含A.class時(shí)就會(huì)優(yōu)先加載炉菲,在后續(xù)的DEX中遇到A.class的話就會(huì)直接返回而不去加載,這樣就達(dá)到了修復(fù)的目的坤溃。
但是有一個(gè)問(wèn)題是拍霜,當(dāng)兩個(gè)調(diào)用關(guān)系的類(lèi)在同一個(gè)DEX時(shí),就會(huì)產(chǎn)生異常報(bào)錯(cuò)薪介。在APK安裝時(shí)祠饺,虛擬機(jī)需要將classes.dex優(yōu)化成odex文件,然后才會(huì)執(zhí)行汁政。在這個(gè)過(guò)程中道偷,會(huì)進(jìn)行類(lèi)的verify操作,如果方法中直接引用到的類(lèi)(第一層級(jí)關(guān)系记劈,不會(huì)進(jìn)行遞歸搜索)和class都在同一個(gè)dex中的話勺鸦,那么這個(gè)類(lèi)就會(huì)被打上CLASS_ISPREVERIFIED,然后才會(huì)寫(xiě)入odex文件目木。所以换途,為了可以正常的進(jìn)行打補(bǔ)丁修復(fù),必須避免類(lèi)被打上CLASS_ISPREVERIFIED標(biāo)志,具體的做法就是單獨(dú)放一個(gè)類(lèi)在另外DEX中军拟,讓其他類(lèi)調(diào)用剃执。
其實(shí)就是兩件事:
1、動(dòng)態(tài)改變BaseDexClassLoader對(duì)象間接引用的dexElements懈息;
2肾档、在app打包的時(shí)候,阻止相關(guān)類(lèi)去打上CLASS_ISPREVERIFIED標(biāo)志辫继。
手機(jī)QQ空間APK具體的實(shí)現(xiàn):先進(jìn)入程序入口QZoneRealApplication阁最,在attachBaseContext中進(jìn)行了兩步操作:(1)修復(fù)CLASS_ISPREVERIFIED標(biāo)志導(dǎo)致的unexpected DEX problem異常。(2)加載修復(fù)的DEX骇两。
整體的流程圖如下:
從流程圖來(lái)看,可以很明顯的找到這種方式的特點(diǎn):
優(yōu)勢(shì):
1姜盈、沒(méi)有合成整包(和微信Tinker比起來(lái))低千,產(chǎn)物比較小,比較靈活
2馏颂、可以實(shí)現(xiàn)類(lèi)替換示血,兼容性高。(某些三星手機(jī)不起作用)
不足:
1.不支持即時(shí)生效救拉,必須通過(guò)重啟才能生效难审。
2.為了實(shí)現(xiàn)修復(fù)這個(gè)過(guò)程,必須在應(yīng)用中加入兩個(gè)dex亿絮!dalvikhack.dex中只有一個(gè)類(lèi)告喊,對(duì)性能影響不大,但是對(duì)于patch.dex來(lái)說(shuō)派昧,修復(fù)的類(lèi)到了一定數(shù)量黔姜,就需要花不少的時(shí)間加載。
3.在ART模式下蒂萎,如果類(lèi)修改了結(jié)構(gòu)秆吵,就會(huì)出現(xiàn)內(nèi)存錯(cuò)亂的問(wèn)題。為了解決這個(gè)問(wèn)題五慈,就必須把所有相關(guān)的調(diào)用類(lèi)纳寂、父類(lèi)子類(lèi)等等全部加載到patch.dex中,導(dǎo)致補(bǔ)丁包異常的大泻拦,進(jìn)一步增加應(yīng)用啟動(dòng)加載的時(shí)候毙芜,耗時(shí)更加嚴(yán)重。
2微信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ù)的目的对嚼。
從流程圖來(lái)看夹抗,同樣可以很明顯的找到這種方式的特點(diǎn):
優(yōu)勢(shì):
1、合成整包纵竖,不用在構(gòu)造函數(shù)插入代碼漠烧,防止verify,verify和opt在編譯期間就已經(jīng)完成靡砌,不會(huì)在運(yùn)行期間進(jìn)行已脓、
2、性能提高通殃。兼容性和穩(wěn)定性比較高度液。
3、開(kāi)發(fā)者透明画舌,不需要對(duì)包進(jìn)行額外處理堕担。
不足:
1、與超級(jí)補(bǔ)丁技術(shù)一樣曲聂,不支持即時(shí)生效霹购,必須通過(guò)重啟應(yīng)用的方式才能生效。
2朋腋、需要給應(yīng)用開(kāi)啟新的進(jìn)程才能進(jìn)行合并齐疙,并且很容易因?yàn)閮?nèi)存消耗等原因合并失敗。
3旭咽、合并時(shí)占用額外磁盤(pán)空間剂碴,對(duì)于多DEX的應(yīng)用來(lái)說(shuō),如果修改了多個(gè)DEX文件轻专,就需要下發(fā)多個(gè)patch.dex與對(duì)應(yīng)的classes.dex進(jìn)行合并操作時(shí)這種情況會(huì)更嚴(yán)重忆矛,因此合并過(guò)程的失敗率也會(huì)更高。
小結(jié)
QQ空間超級(jí)補(bǔ)丁技術(shù)和微信Tinker的修復(fù)原理都基于類(lèi)加載请垛,在功能上已經(jīng)支持類(lèi)催训、資源的替換和新增,功能非常強(qiáng)大宗收。但是也有非常多的問(wèn)題漫拭。
A、多DEX帶來(lái)的性能問(wèn)題和影響
多DEX方案用來(lái)解決應(yīng)用方法數(shù)65k的問(wèn)題混稽,現(xiàn)在Google也官方支持了MultiDex的實(shí)現(xiàn)方案采驻。但是审胚,這實(shí)在是應(yīng)用因方法數(shù)超出而作出的不得已的下策,超級(jí)補(bǔ)丁技術(shù)和Tinker作為一種熱修復(fù)的方案礼旅,平生給應(yīng)用增加了多個(gè)DEX膳叨,而多DEX技術(shù)最大的問(wèn)題在于性能上的坑,因此基于這種方案的補(bǔ)丁技術(shù)影響應(yīng)用的性能是無(wú)疑的痘系。
(a)啟動(dòng)加載時(shí)間過(guò)長(zhǎng)
可以看到菲嘴,超級(jí)補(bǔ)丁技術(shù)和Tinker都選擇在Application的attachBaseContext()進(jìn)行補(bǔ)丁dex的加載,即使這是加載dex的最佳時(shí)機(jī)汰翠,但是依然會(huì)帶來(lái)很大的性能問(wèn)題龄坪,首當(dāng)其沖的就是啟動(dòng)時(shí)間太長(zhǎng)。對(duì)于補(bǔ)丁DEX來(lái)說(shuō)复唤,應(yīng)用啟動(dòng)時(shí)虛擬機(jī)會(huì)將patch.dex文件轉(zhuǎn)換成odex文件健田,這個(gè)過(guò)程非常耗時(shí)。而這個(gè)過(guò)程佛纫,又要求需要在主線程中抄课,以同步的方式執(zhí)行,否則無(wú)法成功進(jìn)行修復(fù)雳旅。就DEX的加載時(shí)間,大概做了以下的時(shí)間測(cè)試间聊。
隨著patch.dex的增加攒盈,在不做任何優(yōu)化的情況下,啟動(dòng)時(shí)間也直線增長(zhǎng)哎榴。
(b)易造成應(yīng)用的ANR和Crash
正是尤其多DEX加載導(dǎo)致了啟動(dòng)時(shí)間過(guò)長(zhǎng)型豁,很容易就會(huì)引發(fā)應(yīng)用的ANR。我們知道當(dāng)應(yīng)用在主線程等待超過(guò)5s以后尚蝌,就會(huì)直接導(dǎo)致長(zhǎng)時(shí)間無(wú)響應(yīng)而退出迎变。超級(jí)補(bǔ)丁技術(shù)為保證ART不出現(xiàn)地址錯(cuò)亂問(wèn)題,需要將所有關(guān)聯(lián)的類(lèi)全部加入到補(bǔ)丁中飘言,而微信Tinker采取一種差量包合并加載的方式衣形,都會(huì)使要加載的dex體積變得很大。這也很大程度上容易導(dǎo)致ANR情況的出現(xiàn)姿鸿。
除了應(yīng)用ANR以外谆吴,多DEX模式也同樣很容易導(dǎo)致Crash情況的出現(xiàn)。我們知道苛预,超級(jí)補(bǔ)丁技術(shù)為了保證ART設(shè)備下不出現(xiàn)地址錯(cuò)亂問(wèn)題句狼,需要把修改類(lèi)的所有相關(guān)類(lèi)全部加入到補(bǔ)丁中,這里會(huì)出現(xiàn)一個(gè)問(wèn)題热某,為了保證補(bǔ)丁包的體積最小腻菇,能否保證引入全部的關(guān)聯(lián)類(lèi)而不引入無(wú)關(guān)的類(lèi)呢胳螟?一旦沒(méi)有引入關(guān)聯(lián)的類(lèi),就會(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)類(lèi)而導(dǎo)致了更嚴(yán)重的Crash蔬捷,就更加的得不償失。
如果我們僅僅就是開(kāi)發(fā)一款app榔袋,沒(méi)有大改動(dòng)周拐,不會(huì)熱更全局變量,不會(huì)增加方法凰兑,那么AndFix框架是首選妥粟。