By吳思博
一、什么是熱修復:
二、技術背景
三、當前主流的熱修復方案比較
四、Andfix的介紹與實踐(開源)
1原理
2基本使用
五、其他方案介紹
一、什么是熱修復:
熱修復(HotFix)是以補丁的方式動態修復緊急Bug,不再需要重新發布App,不需要用戶重新下載覆蓋安裝的方式來實現代碼的替換修改。
二、技術背景
(1)正常開發流程
從流程來看,傳統的開發流程存在很多弊端:
1、重新發布版本代價太大
2、用戶下載安裝成本太高
3、BUG修復不及時,用戶體驗太差
(2)熱修復開發流程
而熱修復的開發流程顯得更加靈活,優勢很多:
1、無需重新發版,實時高效修復
2、用戶無感知修復,無需下載新的應用,代價小
3、修復成功率高,把損失降到最低
三、當前主流的熱修復方案比較:
熱修復技術近期變得越來越熱門,同時也出現了一些不同的解決方案,如阿里的AndFix(開源)、QQ空間補丁方案、以及微信的Tinker方案,但是它們的原理、適用場景都各有不同。我們項目中可以采用哪種方案,是我們比較關注的問題。
QQ空間超級補丁技術和微信Tinker支持新增類和資源的替換,在一些功能化的更新上更為強大,但對應用的性能和穩定會有的一定的影響;AndFix雖然暫時不支持新增類和資源的替換,對新功能的發布也有所限制,作為定位為線上緊急BUG的熱修復的服務來說,還是比較好的,同時對應用性能不產生不必要的損耗,在熱修復方面不失為一個好的選擇。
四、Andfix的介紹與實踐(定位:一個低成本快速接入的熱修復第一方案)
Github:https://github.com/alibaba/AndFix
(1)AndFix是一個Android App的在線熱補丁框架。使用此框架,我們能夠在不重復發版的情況下,在線修改App中的Bug。AndFix就是“android Hot-Fix”的縮寫。就目前來說,AndFix支持Android 2.3到7.0版本,并且支持arm與X86系統架構的設備。支持Dalvik與ART的Runtime。AndFix的補丁文件是以.apatch結尾的文件。
AndFix不同于QQ空間超級補丁技術和微信Tinker通過增加或替換整個DEX的方案,提供了一種運行時在Native修改Filed指針的方式,實現方法的替換,達到即時生效無需重啟,對應用無性能消耗
AndFix實現過程:
對于實現方法的替換,在Native層操作,經過三個步驟:
下面以Dalvik設備為例,來分析具體的實現過程
對于Dalvik來說,遵循JIT即時編譯機制,需要在運行時裝載libdvm.so動態庫,獲取以下內部函數:
1 dvmThreadSelf( ):查詢當前的線程;
2 dvmDecodeIndirectRef():根據當前線程獲得ClassObject對象。
setFieldFlag
動態庫會忽略非public屬性的字段和方法,該操作的目的:讓private、protected的方法和字段可被動態庫看見并識別。
replaceMethod:
AndFix對ART設備同樣支持,具體的過程與Dalvik相似。
該步驟是方法替換的核心,替換的流程如下:
優點:
1、BUG修復的即時
2、補丁包同樣采用差量技術,生成的PATCH體積小
3、對應用無侵入,幾乎無性能損耗
不足:
1、不支持新增字段,以及修改方法,也不支持對資源的替換。
2、由于廠商的自定義ROM,對少數機型可能暫時不支持。
(2)Andfix的基本使用。
1.在自定義Application中初始化,為了更早的修復應用中的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管理類
mPatchManager = new PatchManager(this);
//初始化patch版本
mPatchManager.init("1.0");
//加載已經添加到PatchManager中的patch
mPatchManager.loadPatch();
}
}
2.如果有新的補丁需要修復,下載完成后,進行以下操作
//添加patch,只需指定patch的路徑即可,補丁會立即生效
mPatchManager.addPatch(path);
3.當apk版本升級,需要把之前patch文件的刪除,需要以下操作
//刪除所有已加載的patch文件
mPatchManager.removeAllPatch();
我們也可封裝成一個工具類
patch文件的生成
使用工具:apkpatch-1.0.3
原理:根據兩個apk包,生成一個差異文件,就是所謂的補丁文件即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用戶別名密碼
執行完命令,就會在輸出目錄中輸出.apatch文件:
new-c293df7dbc23f11214fdd020ea78d3b8.apatch
:就是patch文件。
.apatch文件根目錄內容:
META_INF
文件下內容:
PATCH.MF
文件內容:
注:Patch-Classes就是改動過的class.
客戶端請求服務器接口(api),服務器根據用戶傳遞的數據分析是否有需要修復的bug。如果有bug需要修復,就下載服務器指定的.apatch文件的鏈接,下載完后及時加載并修復,使用addpatch(path)方法,補丁會立即生效。
dependencies {
compile 'com.alipay.euler:andfix:0.3.1@aar'
}
-keep class * extends java.lang.annotation.Annotation
-keepclasseswithmembernames class * {
native ;
}
Q & A:
1、如何解決某些機型不兼容的問題?還有采用YunOS系統的?之前測試在ART模式機型上,偶現崩潰,不知采用了什么機制處理的?線上采用Andfix,會不會風險比較高?
ART的機型上暫時未出現崩潰的現象。對于機型的兼容性問題,采取的措施是做平臺區間控制基本兼容,對于個別機型的問題,可以參考統計數據加入黑名單,與服務器約定協議,黑名單上的機型不走andifx流程。美聊線上實踐暫時未出現不可控的問題
2、沒有成功更新的情況下,會不會引起app的崩潰?
更新不成功不會引起崩潰
五、其他技術簡介
android的類加載器分為兩種,PathClassLoader和DexClassLoader,兩者都繼承自BaseDexClassLoader
PathClassLoader用來加載系統類和應用類。DexClassLoader用來加載jar、apk、dex文件.加載jar、apk也是最終抽取里面的Dex文件進行加載。
1、QQ空間超級補丁技術
QQ空間超級補丁技術基于DEX分包方案,使用了多DEX加載的原理,大致的過程就是:把BUG方法修復以后,放到一個單獨的DEX里,插入到dexElements數組的最前面,讓虛擬機去加載修復完后的方法。
當patch.dex中包含A.class時就會優先加載,在后續的DEX中遇到A.class的話就會直接返回而不去加載,這樣就達到了修復的目的。
但是有一個問題是,當兩個調用關系的類在同一個DEX時,就會產生異常報錯。在APK安裝時,虛擬機需要將classes.dex優化成odex文件,然后才會執行。在這個過程中,會進行類的verify操作,如果方法中直接引用到的類(第一層級關系,不會進行遞歸搜索)和class都在同一個dex中的話,那么這個類就會被打上CLASS_ISPREVERIFIED,然后才會寫入odex文件。所以,為了可以正常的進行打補丁修復,必須避免類被打上CLASS_ISPREVERIFIED標志,具體的做法就是單獨放一個類在另外DEX中,讓其他類調用。
其實就是兩件事:
1、動態改變BaseDexClassLoader對象間接引用的dexElements;
2、在app打包的時候,阻止相關類去打上CLASS_ISPREVERIFIED標志。
手機QQ空間APK具體的實現:先進入程序入口QZoneRealApplication,在attachBaseContext中進行了兩步操作:(1)修復CLASS_ISPREVERIFIED標志導致的unexpected DEX problem異常。(2)加載修復的DEX。
整體的流程圖如下:
從流程圖來看,可以很明顯的找到這種方式的特點:
優勢:
1、沒有合成整包(和微信Tinker比起來),產物比較小,比較靈活
2、可以實現類替換,兼容性高。(某些三星手機不起作用)
不足:
1.不支持即時生效,必須通過重啟才能生效。
2.為了實現修復這個過程,必須在應用中加入兩個dex!dalvikhack.dex中只有一個類,對性能影響不大,但是對于patch.dex來說,修復的類到了一定數量,就需要花不少的時間加載。
3.在ART模式下,如果類修改了結構,就會出現內存錯亂的問題。為了解決這個問題,就必須把所有相關的調用類、父類子類等等全部加載到patch.dex中,導致補丁包異常的大,進一步增加應用啟動加載的時候,耗時更加嚴重。
2微信Tinker
微信針對QQ空間超級補丁技術的不足提出了一個提供DEX差量包,整體替換DEX的方案。主要的原理是與QQ空間超級補丁技術基本相同,區別在于不再將patch.dex增加到elements數組中,而是差量的方式給出patch.dex,然后將patch.dex與應用的classes.dex合并,然后整體替換掉舊的DEX,達到修復的目的。
從流程圖來看,同樣可以很明顯的找到這種方式的特點:
優勢:
1、合成整包,不用在構造函數插入代碼,防止verify,verify和opt在編譯期間就已經完成,不會在運行期間進行、
2、性能提高。兼容性和穩定性比較高。
3、開發者透明,不需要對包進行額外處理。
不足:
1、與超級補丁技術一樣,不支持即時生效,必須通過重啟應用的方式才能生效。
2、需要給應用開啟新的進程才能進行合并,并且很容易因為內存消耗等原因合并失敗。
3、合并時占用額外磁盤空間,對于多DEX的應用來說,如果修改了多個DEX文件,就需要下發多個patch.dex與對應的classes.dex進行合并操作時這種情況會更嚴重,因此合并過程的失敗率也會更高。
小結
QQ空間超級補丁技術和微信Tinker的修復原理都基于類加載,在功能上已經支持類、資源的替換和新增,功能非常強大。但是也有非常多的問題。
A、多DEX帶來的性能問題和影響
多DEX方案用來解決應用方法數65k的問題,現在Google也官方支持了MultiDex的實現方案。但是,這實在是應用因方法數超出而作出的不得已的下策,超級補丁技術和Tinker作為一種熱修復的方案,平生給應用增加了多個DEX,而多DEX技術最大的問題在于性能上的坑,因此基于這種方案的補丁技術影響應用的性能是無疑的。
(a)啟動加載時間過長
可以看到,超級補丁技術和Tinker都選擇在Application的attachBaseContext()進行補丁dex的加載,即使這是加載dex的最佳時機,但是依然會帶來很大的性能問題,首當其沖的就是啟動時間太長。對于補丁DEX來說,應用啟動時虛擬機會將patch.dex文件轉換成odex文件,這個過程非常耗時。而這個過程,又要求需要在主線程中,以同步的方式執行,否則無法成功進行修復。就DEX的加載時間,大概做了以下的時間測試。
隨著patch.dex的增加,在不做任何優化的情況下,啟動時間也直線增長。
(b)易造成應用的ANR和Crash
正是尤其多DEX加載導致了啟動時間過長,很容易就會引發應用的ANR。我們知道當應用在主線程等待超過5s以后,就會直接導致長時間無響應而退出。超級補丁技術為保證ART不出現地址錯亂問題,需要將所有關聯的類全部加入到補丁中,而微信Tinker采取一種差量包合并加載的方式,都會使要加載的dex體積變得很大。這也很大程度上容易導致ANR情況的出現。
除了應用ANR以外,多DEX模式也同樣很容易導致Crash情況的出現。我們知道,超級補丁技術為了保證ART設備下不出現地址錯亂問題,需要把修改類的所有相關類全部加入到補丁中,這里會出現一個問題,為了保證補丁包的體積最小,能否保證引入全部的關聯類而不引入無關的類呢?一旦沒有引入關聯的類,就會出現以下的異常:
NoClassDefFoundError
Could not find class
Could not find method
出現這些異常,就會直接導致應用的Crash退出。所以,不難看出如果我們需要修復一個不是Crash的BUG,但是因為未加入相關類而導致了更嚴重的Crash,就更加的得不償失。
如果我們僅僅就是開發一款app,沒有大改動,不會熱更全局變量,不會增加方法,那么AndFix框架是首選。