Android熱修復框架學習及應用

寫在開頭

從15年開始各技術大佬們開始研究熱修復技術,並陸續開源了許多的熱修復框架。如Jasonross 的Nuwa,美團的Robust,阿里的Andfix,騰訊的Tinker 等等…均是Android 前輩們夜以繼日的成果。而現在熱修復被廣泛地應用於Android 應用和遊戲,運用並理解熱修復框架在面試中也是加分項。所以,趕緊學起來吧…
本文以Tinker 作爲學習對象,主要講述各開源框架的對比和記錄Tinker 的Demo 集成過程。

開源框架對比

這一節篇幅較長,主要是用自己的話來總結各熱修復框架的實現原理。如果只想看Tinker接入實現的同學可跳過本節,進入下一章節。

Nuwa 實現原理:

最早看到熱修復框架的相關文章就是Qzone官方的文章,但是Qzone熱修復技術的實現代碼並沒有開源。不過GitHub上有一開源的熱修復框架Nuwa,實現原理和其相似。這裏我們以Qzone爲例進行分析。

Qzone的實現原理是生成差分dex文件,將patch.dex插到dexElements的最前面。但patch.dex中的類patchA.java會引用classes.dex中的類classesB.java,如果這兩個類所在的patch.dex和classes.dex不是同一個文件就會報錯。

疑惑的Qzone技術大佬發現classes.dex和classes2.dex也不是同一個文件,爲啥不報錯?於是繼續查,發現了這個判斷

if(!fromUnverifiedConstant && IS_CLASS_FLAG_SET(referrer, CLASS_ISPREVERIFIED)){

如果被引用者(也就是classesB.java)這個類被打上了CLASS_ISPREVERIFIED標誌,那麼就會進行dex的校驗。校驗不過就報錯了。

那麼這個標誌是什麼時候被打上去的?
在apk安裝的時候,虛擬機會將dex優化成odex後纔拿去執行。在這個過程中會對所有class進行校驗。

怎麼校驗的?
假設classesB.java類在它的static方法,private方法,構造函數,override方法中直接引用到classesC.java類。如果classesB.java類和classesC.java類在同一個dex中,那麼classesB.java類就會被打上CLASS_ISPREVERIFIED標記,被打上這個標記的類不能被其他dex中的類引用,否則就會報錯。所以要防止類被打上CLASS_ISPREVERIFIED的標誌。

如何防止類被打上CLASS_ISPREVERIFIED的標誌?
Common類會被打包成單獨的hack.dex,這樣當安裝apk的時候,classes.dex內的類都會引用一個在不相同dex中的Common類,這樣就防止了類被打上CLASS_ISPREVERIFIED的標誌了,只要沒被打上這個標誌的類都可以進行打補丁操作。

優點:

  • 開發透明,簡單;
  • 熱修復成功率高。

缺點:

  • 在Art 上表現爲補丁包較大;
  • 在Dalvik 上性能消耗較大。

Robust實現原理:

Robust 插件對每個產品代碼的每個函數都在編譯打包階段自動的插入了一段代碼。通過判斷 if(changeQuickRedirect != null) 來確定是否進行熱修復,當 changeQuickRedirect 不爲 null 時,調用 patch.dex 中同名類的同名方法達到 hotfix 的目的。

如何調用呢?
生成的patch.dex 中有兩個主要類,PatchesInfoImpl.java 和修復後的同名類 APatch.java。客戶端拿到patch.dex 後,用DexClassLoader 加載patch.dex,反射拿到PatchesInfoImpl.java 這個 class。並創建這個class 的一個對象。然後通過這個對象知道被替換的是誰,給它的變量changeQuickRedirect 賦值爲patch.dex中的APatch的對象,這樣就會去執行補丁包中的方法了。

大致流程圖如下所示:A_old表示未被修復的原有類,A_new表示已修復的新類。

Created with Raphaël 2.1.2開始changeQuick-Redirect != nullDexClassLoader 加載 patch.dex反射獲取 PatchesInfoImpl 對象獲取被修復的類名及方法名反射得到 A_old 的對象給 A_old 類中的 changeQuick-Redirect 對象(此時爲null)賦值爲A_new類的對象執行A_new類中的代碼結束執行原有代碼yesno

優點

  • 兼容性高,開發透明;
  • 實時生效。

缺點

  • 會增大方法數,影響運行效率;
  • 暫不支持 so 文件和資源的替換。

Andfix實現原理:

Andfix 採用的方法是,在已經加載了的類中直接在 native 層替換掉原有方法,是在原來類的基礎上進行修改的。其核心在於 replaceMethod 函數,所以只支持方法替換,對於方法的增刪,資源更新,so 文件更新及類和屬性的替換等都是不支持的。

Andfix 的實現偏 native 層,筆者能力有限,其具體實現過程,就不妄加總結了。
更多實現細節請看 Andfix 官網文章

優點

  • 立即生效,消耗低;
  • 補丁包較小。

缺點

  • 僅支持方法替換;
  • 兼容性不佳,對部分機型暫不支持。

Tinker實現原理:

在 App 運行到一半的時候,所有需要發生變更的 Class 已經被加載過了,在Android 上是無法對一個 Class 進行卸載的。而Tinker的方案,都是讓 Classloader 去加載新的類。如果不重啓,原來的類還在虛擬機中,就無法加載新類。因此,只有在下次重啓的時候,在還沒走到業務邏輯之前搶先加載補丁中的新類,這樣後續訪問這個類時,就會 Resolve 爲新的類。從而達到熱修復的目的。

Tinker 的實現過程更像是在 Qzone 熱修復方案上做優化。核心點是性能最優,消耗最低。

經 Tinker 開發人員調研,Qzone 的方案最大挑戰在於性能,即Dalvik平臺存在插樁導致的性能損耗,Art平臺由於地址偏移問題導致補丁包可能過大的問題。爲了避免這兩個問題,根據 Instant Run 的全量替換新的 Dex 的思路,於是決定將新舊兩個Dex 的差異放到補丁包中。

經過調研,BsDiff 算法對 Dex 支持效果不太好,所以,Tinker 開發團隊人員自研了 DexDiff 算法。

最終, BsDiff 加載 so 和部分資源文件,DexDiff 加載 Dex文件,以達到性能最優。但是這個方案也有缺點,就是佔用 ROM 較大。好吧!現在手機內存都不小,多幾十 M 可以接受。

優點

  • 補丁包較小,消耗較小;
  • 開發透明,文檔豐富。

缺點

  • 佔用 ROM 較大;
  • 需要重啓才能生效。

對比與選擇:

Type Nuwa Robust Andfix Tinker
Company Null Meituan Alibaba Tencent
開發時間 2015 2016 2015 2016
替換類 X X
替換So X X X
替換資源 X X
即時生效 X X
成功率 較高 最高 一般 較高
接口文檔 ★★ ★★ ★★ ★★★★

單就熱修復線上 APP 某一處或多處 bug 來說,Andfix能做到即時修復,且操作簡單,不用生成較多的 patch.dex 包,能輕鬆解決緊急問題。

但對於如非緊急 bug 的修復及小版本的發佈,對即時生效性要求不高的情況,Tinker 支持的替換內容較豐富,更勝一籌。

阿里將 Andfix 升級爲商業版 SDK Sophix;騰訊將 Tinker 升級爲 Bugly。
Sophix 不但支持即時修復,還支持再次啓動修復類、so 文件、資源等。作爲商用 APP 集成 Sophix 是很好的選擇。
但 Tinker 的開源,爲其帶來了大量的使用者和測試者,除此以外還與各大手機廠商建立聯繫,使得各廠商在系統定製時也會考慮是否影響熱修復的問題。所以 Tinker 的兼容性可見一斑。

總的來說,各有千秋,各需所需吧。
我們這裏以 Tinker 爲學習對象,接下來先讓 Tinker-Demo 跑起來,看一下實際效果。

Tinker-Demo 效果

下載 Github 上的開源代碼,然後僅需導入 tinker-sample-android 工程即可。

添加依賴

在項目的 build.gradle 中,添加 tinker-patch-gradle-plugin 的依賴

buildscript {
    dependencies {
        classpath ('com.tencent.tinker:tinker-patch-gradle-plugin:1.9.1')
    }
}

然後在app的gradle文件app/build.gradle,我們需要添加tinker的庫依賴以及apply tinker的gradle插件.

dependencies {
    //可選,用於生成application類 
    provided('com.tencent.tinker:tinker-android-anno:1.9.1')
    //tinker的核心庫
    compile('com.tencent.tinker:tinker-android-lib:1.9.1') 
}
...
//apply tinker插件
apply plugin: 'com.tencent.tinker.patch'

準備好後,運行…

tinkerId is not set!!!

看一下app/build.gradle中在哪裏設置tinkerId。

tinkerId

def getTinkerIdValue() {
    return hasProperty("TINKER_ID") ? TINKER_ID : gitSha()
}
def gitSha() {
        String gitRev = 'git rev-parse --short HEAD'.execute(null, project.rootDir).text.trim()
        ...

Tinker常見問題文檔

tinkerId is not set 官網回答

這裏設置成版本號即可

String gitRev = '1.9.1'

再運行…
點擊 SHOW INFO 按鈕

這裏寫圖片描述

生成補丁包

MainActivity.java中添加代碼

Toast.makeText(this, "hello, Tinker", Toast.LENGTH_SHORT).show();

在app/build.gralde中,將剛纔生成的apk包標記爲oldApk

if (buildWithTinker()) {
    apply plugin: 'com.tencent.tinker.patch'
    tinkerPatch {
    ...
        oldApk = getOldApkPath()
def getOldApkPath() {
    return hasProperty("OLD_APK") ? OLD_APK : ext.tinkerOldApkPath
}

oldApkPath

包名改成和左邊的一樣。
在底部Terminal中輸入生成補丁包的命令 graldew tinkerPatchDebug

報錯

com.tencent.tinker.build.util.TinkerPatchException:
Warning: ignoreWarning is false, manifest was changed, while hot plug component support mode is disabled. Such changes will not take effect.

搜了下Issues,有相同的問題。官方技術大佬是怎麼回覆的,不過具體原因還有待研究…

官方技術大佬的回覆

ignoreWarning = true 這裏設置爲忽略警告,再次 graldew tinkerPatchDebug

成功之後有個patch_signed_7zip.apk

patch_signed_7zip.apk

下載併合成補丁

可以使用命令行將補丁包發送到手機。
adb push ./app/build/outputs/apk/tinkerPatch/debug/patch_signed_7zip.apk /storage/sdcard0/

不過這裏運行失敗了
adb server is out of date. killing...
CreateProcess failure, error 2
* failed to start daemon *
error:

試了一大堆方法,無果…

好吧!手動拷貝到手機文件管理根目錄下。
再次打開Tinker-Demo
點擊LOAD PATCH 按鈕
過了2-3s 出現Toast 提示

Toast 提示

返回,再進入…
沒反應
這裏注意,必須要殺掉進程,再次進來才能成功加載patch包的代碼。
手動殺掉,或者點擊KILL SELF 按鈕

hello, Tinker

完成修復

寫在後頭

各個框架各有優劣,Tinker 官方在文檔中也指出其不足之處:

Tinker經過幾次全量上線,也發現了一些熱補丁的問題。有以下的一些優化工作尚未完成:
1. 支持四大組件的代理;
2. Crash 啓動保護;

道阻且長,目前Tinker 還只能是替換類、資源和so 文件等,如果支持了四大組件的代理,也許所有的非重大版本更新都可以用熱修復來實現了。

記錄在此,僅爲學習!
感謝您的閱讀!歡迎指正!
歡迎加入 Android 技術交流羣,羣號:155495090

參考文章

  1. Qzone-安卓App熱補丁動態修復技術介紹 (https://mp.weixin.qq.com/s?__biz=MzI1MTA1MzM2Nw==&mid=400118620&idx=1&sn=b4fdd5055731290eef12ad0d17f39d4a)
  2. 美團點評Robust (https://tech.meituan.com/android_robust.html)
  3. Android熱修復升級探索 (https://yq.aliyun.com/articles/74598?spm=5176.100239.blogcont103527.13.9ka3bq#1)
  4. Tinker – 微信Android熱補丁方案 (https://github.com/Tencent/tinker/wiki)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章