去年一整年android社區中刮過了一陣熱修復的風,各大廠商,逼格大牛紛紛開源了熱修復框架,恩,產品過程中怎么可能沒有bug呢?重新打包上線?成本太高用戶體驗也不好,咋辦?上熱修復唄。
好吧,既然要開始上熱修復的功能,那么就得調研一下熱修復的原理。下面我將分別講述一下熱修復的原理,各大熱修復框架的比較,以及自身產品中熱修復功能的實踐。
**熱修復的原理**
1. 通過更改dex加載順序實現熱修復
最新github上開源了很多熱補丁動態修復框架,大致有:
[HotFix ](https://github.com/dodola/HotFix) [Nuwa ](https://github.com/jasonross/Nuwa) [DroidFix ](https://github.com/bunnyblue/DroidFix)
上述三個框架呢,根據其描述,原理都來自:
[安卓App熱補丁動態修復技術介紹](https://mp.weixin.qq.com/s?__biz=MzI1MTA1MzM2Nw==&mid=400118620&idx=1&sn=b4fdd5055731290eef12ad0d17f39d4a&scene=1&srcid=1106Imu9ZgwybID13e7y2nEi#wechat_redirect),以及[Android dex分包方案](http://my.oschina.net/853294317/blog/308583),其核心原理就是通過更改含有bug的dex文件的加載順序。在dex的加載中,若以找到方法則不會繼續查找,所以如果能讓修復之后的方法在含有bug的方法之前加載就能達到修復bug的目的,而這個框架都是基于這個思路實現的,當然了具體實現過程中有很多坑,大家可以參考一下上面提到的兩篇文章。
2. 通過Native替換方法指針的方式實現熱修復
這里主要是阿里開源的兩個熱修復框架:[Dexpost ](https://github.com/alibaba/dexposed) [AndFix](https://github.com/alibaba/AndFix)都是通過Native層使用指針替換的方法替換bug,達到修復bug的目的的,具體可參考其github文章。
**各大熱修復框架的比較**
這里暫時比較了Dexpost,AndFix,HotFix,Nuwa,DroidFix等框架。
* Dexpost:(未測試)
1)原理:在底層虛擬機運行時hoop方法;
2)地址:https://github.com/alibaba/dexposed;
3)缺點:適配方面存在一些問題,目前不支持android6.0,5,1;art運行時;
4)優點:無需重啟就可以達到修復bug的目的;
* AndFix:(已測試)
1)原理:在Native層使用指針替換的方法替換bug方法,達到修復bug的目的;
2)地址:https://github.com/alibaba/AndFix;
3)缺點:底層替換,穩定性方面可能需要實際檢測;
4)優點:無需中期就可以達到修復bug的目的;
* HotFix:(個人維護,可能存在坑)
1)原理:通過替換類加載器中bugclass,達到修復bug的目的;
2)地址:https://github.com/dodola/HotFix;
3)缺點:需要重新啟動才可以修復bug;
4)優點:java運行層修復,穩定性較好;
* Nuwa:(個人維護,可能存在坑)
1)原理:通過替換類加載器中bugclass,達到修復bug的目的;
2)地址:https://github.com/jasonross/Nuwa;
3)缺點:需要重新啟動才可以修復bug;
4)優點:java運行層修復,穩定性較好;
* DroidFix:(個人維護,可能存在坑)
1)原理:通過替換類加載器中bugclass,達到修復bug的目的;
2)地址:https://github.com/bunnyblue/DroidFix;
3)缺點:需要重新啟動才可以修復bug;
4)優點:java運行層修復,穩定性較好;
**其他的方面**:
Dexposed不支持Art模式(5.0+),且寫補丁有點困難,需要反射寫混淆后的代碼,粒度太細,要替換的方法多的話,工作量會比較大。
AndFix支持2.3-6.0,但是不清楚是否有一些機型的坑在里面,畢竟jni層不像java曾一樣標準,從實現來說,方法類似Dexposed,都是通過jni來替換方法,但是實現上更簡潔直接,應用patch不需要重啟。但由于從實現上直接跳過了類初始化,設置為初始化完畢,所以像是靜態函數、靜態成員、構造函數都會出現問題,復雜點的類Class.forname很可能直接就會掛掉。
ClassLoader方案支持2.3-6.0,會對啟動速度略微有影響,只能在下一次應用啟動時生效,在空間中已經有了較長時間的線上應用,如果可以接受在下次啟動才應用補丁,是很好的選擇。
總的來說,在兼容性穩定性上, ClassLoader方案很可靠 ,如果需要應用 不重啟就能修復 ,而且方法足夠簡單,可以使用 AndFix ,而 Dexposed由于還不能支持art,所以只能暫時放棄,希望開發者們可以改進使它能支持art模式,畢竟xposed的種種能力還是很吸引人的。
**熱修復實踐**
最終我們App的熱修復方案選擇的是AndFix,原因有三:
(1)AndFix支持android2.3-6.0,所以在機型上的是適配上是沒問題的;
(2)AndFix是由阿里開源的,并且持續維護中,目前不少公司已經使用其作為自身App的熱修復方案;
(3)通過修改Dex加載順序的方式實現熱修復需要重新啟動App,并且相應的開源框架多多少少存在著問題,沒有持續的維護;
因此我們最終選擇了AndFix作為我們的開源方案。具體的AndFix集成方式可參考[github中AndFix的介紹](https://github.com/alibaba/AndFix)
這里簡單介紹一下具體的繼承流程
(1)在App的Application的onCreate方法中執行AndFix的初始化操作;
(2)判斷服務器端是否有可更新的熱修復差異包
(3)若無則直接退出,若有則下載并執行修復動作
(4)修復完成之后刪除下載的補丁差異包
(5)在判斷服務器端是否有可更新的補丁包的時候可添加灰度,如版本,渠道,用戶等,實現對補丁包定制化的修復
另外需要說明的是:若一個版本中存在著多個bug,則一般的都是讓后一個補丁包覆蓋前一個補丁包,并刪除前一個補丁包,簡單來說就是對于每一個版本至多有一個補丁包。
最后貼上App端AndFix的實現源碼:
~~~
/**
* Created by aaron on 2016/3/7.
* 主要用于實現熱修復邏輯
* 采用阿里巴巴開源框架-andfix
*
*/
public class AndfixManager {
public static final String TAG = AndfixManager.class.getSimpleName();
// AndfixManager單例對象
private static AndfixManager instance = null;
// 補丁文件名稱
public static final String PATCH_FILENAME = "/patchname.apatch";
public static PatchManager patchManager = null;
private AndfixManager() {}
/**
* 線程安全之懶漢模式實現單例模型
* @return
*/
public static synchronized AndfixManager getInstance() {
return instance == null ? new AndfixManager() : instance;
}
/**
* 執行andfix初始化操作
*/
public static void init(Context mContext) {
if (mContext == null) {
L.i("初始化熱修復框架,參數錯誤!!!");
return;
}
patchManager = new PatchManager(mContext);
// 初始化patch版本,這里初始化的是當前的App版本;
patchManager.init(VersionUtils.getVersionName(mContext));
// 加載已經添加到PatchManager中的patch
patchManager.loadPatch();
downLoadAndAndPath(mContext);
}
/**
* 請求服務器獲取補丁文件并加載
*/
public static void downLoadAndAndPath(final Context mContext) {
// 請求服務器獲取差異包
ExtInterface.GetShContent.Request.Builder request = ExtInterface.GetShContent.Request.newBuilder();
// 獲取本地保存的補丁包版本號
final String patchVersion = AndfixSp.getPatchVersion(mContext);
L.i(TAG, "patchVersion:" + patchVersion);
if (!TextUtils.isEmpty(patchVersion)) {
request.setShVersion(patchVersion);
} else {
request.setShVersion("0");
}
NetworkTask task = new NetworkTask(Cmd.CmdCode.GetShContent_SSL_VALUE);
task.setBusiData(request.build().toByteArray());
NetworkUtils.executeNetwork(task, new HttpResponse.NetWorkResponse<UUResponseData>() {
@Override
public void onSuccessResponse(UUResponseData responseData) {
if (responseData.getRet() == 0) {
try {
ExtInterface.GetShContent.Response response = ExtInterface.GetShContent.Response.parseFrom(responseData.getBusiData());
// 若返回成功,則更新腳本下載補丁包
if (response.getRet() == 0) {
ByteString zipDatas = response.getContent();
// 數據解壓縮
byte[] oriDatas = GZipUtils.decompress(zipDatas.toByteArray());
String patchFileName = mContext.getCacheDir() + PATCH_FILENAME;
L.i(TAG, "patchFileName:" + response.getShVersion());
// 將byte數組數據寫入文件
boolean boolResult = getFileFromBytes(patchFileName, oriDatas);
// 寫入文件成功則加載
if (boolResult) {
patchManager.removeAllPatch();
patchManager.addPatch(patchFileName);
// 保存補丁版本號
AndfixSp.putPatchVersion(mContext, response.getShVersion());
// 刪除補丁文件
File files = new File(patchFileName);
if (files.exists()) {
files.delete();
}
}
} else {
// -1 請求失敗
// 1 請求成功,但是沒有更新版本的腳本
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
@Override
public void onError(VolleyError errorResponse) {
}
@Override
public void networkFinish() {
}
});
}
/**
* 根據數組獲取文件
* @param path
* @param oriDatas
*/
public static boolean getFileFromBytes(String path, byte[] oriDatas) {
boolean result = false;
if (TextUtils.isEmpty(path)) {
return result;
}
if (oriDatas == null || oriDatas.length == 0) {
return result;
}
try {
FileOutputStream fos = new FileOutputStream(path);
fos.write(oriDatas);
fos.close();
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
}
~~~
**總結**: android的熱修復原理大體上分為兩種,其一是通過dex的執行順序實現Apk熱修復的功能,但是其需要將App重啟才能生效,其二是通過Native修改函數指針的方式實現熱修復,有興趣的同學可以深入研究。
[android產品研發(一)-->實用開發規范 ](http://blog.csdn.net/qq_23547831/article/details/51534013)
[android產品研發(二)-->啟動頁優化 ](http://blog.csdn.net/qq_23547831/article/details/51541277)
[android產品研發(三)-->基類Activity ](http://blog.csdn.net/qq_23547831/article/details/51546974)
[android產品研發(四)-->減小Apk大小](http://blog.csdn.net/qq_23547831/article/details/51559066)
[android產品研發(五)-->多渠道打包](http://blog.csdn.net/qq_23547831/article/details/51569261)
[Android產品研發(六)–>Apk混淆](http://blog.csdn.net/qq_23547831/article/details/51581491)
- 前言
- (一)–>實用開發規范
- (二)-->啟動頁優化
- (三)-->基類Activity
- (四)-->減小Apk大小
- (五)-->多渠道打包
- (六)-->Apk混淆
- (七)-->Apk熱修復
- (八)-->App數據統計
- (九)-->App網絡數據解析
- (十)-->盡量不使用靜態變量保存數據
- (十一)-->應用內跳轉Scheme協議
- (十二)-->App長連接實現
- (十三)-->App輪詢操作
- (十四)-->App升級與更新
- (十五)-->內存對象序列化
- (十六)-->開發者選項
- (十七)-->Hybrid開發
- (十八)-->webview問題集錦
- (十九)-->Android studio中的單元測試
- (二十)-->代碼Review
- (二十一)-->Android中的UI優化
- (二十二)-->Android實用調試技巧
- (二十三)-->Android中保存靜態秘鑰實踐
- (二十四)-->內存泄露場景與檢測
- (二十五)-->MVC/MVVM/MVP簡單理解