熱修復(fù)框架Tinker初探

Tinker與其他熱修復(fù)框架對比

總結(jié)

  1. 阿里的AndFix作為native解決方案,首先面臨的是穩(wěn)定性與兼容性問題,更重要的是它無法實現(xiàn)類替換,它是需要大量額外的開發(fā)成本的;
  2. 美團的Robust兼容性與成功率最高,但是它與AndFix一樣,無法新增變量與類只能用做的bugFix方案,并且尚未開源,不過有參照Robust的install run原理實現(xiàn)的開源方案,不過較少人關(guān)注,實際效果未知。
  3. 百度金融的RocooFix是Nuwa方案的改良版,增加了lib替換和即時生效支持,但是不支持在windows平臺生成補丁,兼容性還有待測試。
  4. 餓了么的Amigo是非常強大的一個方案,不僅是類替換,lib替換,資源替換都支持,同時也支持新增四大組件,缺點是不支持Android 3.0 ,notification & widget中RemoteViews的自定義布局不支持修改,只支持內(nèi)容修復(fù)。

Amigo官方wiki介紹

Amigo 原理與 QQZone
的方案有些類似,QQZone,Tinker,Nuwa這類方案是通過修改PathClassLoader中的dex實現(xiàn)的,Amigo則是釜底抽薪直接替換ClassLoader。同時進一步實現(xiàn)了
so 文件、資源文件、四大組件的修復(fù),可以對APP全面進行修復(fù)

  1. 微信的Tinker是各方面都比較優(yōu)秀的方案,畢竟經(jīng)過了幾億微信用戶的驗證。Tinker的優(yōu)點上圖已經(jīng)很明確了,而存在的缺陷有以下幾方面:
  • Tinker不支持修改AndroidManifest.xml,Tinker不支持新增四大組件;
  • 由于Google Play的開發(fā)者條款限制,不建議在GP渠道動態(tài)更新代碼;
  • 在Android N上,補丁對應(yīng)用啟動時間有輕微的影響;
  • 不支持部分三星android-21機型,加載補丁時會主動拋出"TinkerRuntimeException:checkDexInstall failed";
  • 由于各個廠商的加固實現(xiàn)并不一致,在1.7.6以及之后的版本,tinker不再支持加固的動態(tài)更新;(由于360電子市場必須經(jīng)過加固應(yīng)用才能上架,因此可以說tinker無法在360渠道上的apk實現(xiàn)熱更新)
  • 對于資源替換,不支持修改remoteView。例如transition動畫,notification icon以及桌面圖標。
  • 與超級補丁技術(shù)一樣,不支持即時生效,必須通過重啟應(yīng)用的方式才能生效。
  • 需要給應(yīng)用開啟新的進程才能進行合并,并且很容易因為內(nèi)存消耗等原因合并失敗。
  • 合并時占用額外磁盤空間,對于多DEX的應(yīng)用來說,如果修改了多個DEX文件,就需要下發(fā)多個patch.dex與對應(yīng)的classes.dex進行合并操作時這種情況會更嚴重,因此合并過程的失敗率也會更高。
  • 接入tinker sdk略微復(fù)雜

Tinker接入

1. 添加gradle依賴

  1. 首先在項目的gradle.properties文件指定Tinker版本,這樣只需修改此處版本號就能更改Tinker版本。加入以下屬性:
TINKER_VERSION=1.7.7
  1. 在項目的build.gradle中,添加tinker-patch-gradle-plugin的依賴
  classpath "com.tencent.tinker:tinker-patch-gradle-plugin:${TINKER_VERSION}"
  1. 然后在app的gradle文件app/build.gradle,我們需要添加tinker的庫依賴以及apply tinker的gradle插件.
    compile("com.tencent.tinker:tinker-android-lib:${TINKER_VERSION}") { changing = true }
    provided("com.tencent.tinker:tinker-android-anno:${TINKER_VERSION}") { changing = true }

其中,tinker-android-anno用于注解生成application類
tinker-android-lib為tinker的核心庫

  1. 在app的gradle文件app/build.gradle配置tinkerPatch task,下面給出簡單的示例:
    //全局信息相關(guān)的配置項
tinkerPatch {
    //有問題的apk的地址  準apk包的路徑,必須輸入,否則會報錯
    oldApk = "/Users/littlebyte/AndroidStudioProjects/TInkerTest/app/oldApk/app-debug.apk"
    //
    ignoreWarning = false
    //在運行過程中,需要驗證基準apk包與補丁包的簽名是否一致,我們是否需要為你簽名
    useSign = true
    //編譯相關(guān)的配置項
    buildConfig {
        //在運行過程中,我們需要驗證基準apk包的tinkerId是否等于補丁包的tinkerId。
        // 這個是決定補丁包能運行在哪些基準包上面,一般來說我們可以使用git版本號、versionName等等。
        tinkerId = "1.0"
    }
    //用于生成補丁包中的'package_meta.txt'文件
    packageConfig {
        //onfigField("key", "value"), 默認我們自動從基準安裝包與新安裝包的Manifest中讀取tinkerId,并自動寫入configField。
        // 在這里,你可以定義其他的信息,在運行時可以通過TinkerLoadResult.getPackageConfigByName得到相應(yīng)的數(shù)值。
        // 但是建議直接通過修改代碼來實現(xiàn),例如BuildConfig。
//        configField("TINKER_ID", "1.0")
    }
    //dex相關(guān)的配置項
    dex {
        //只能是'raw'或者'jar'。
        //對于'raw'模式,我們將會保持輸入dex的格式。
        //對于'jar'模式,我們將會把輸入dex重新壓縮封裝到j(luò)ar。
        // 如果你的minSdkVersion小于14,你必須選擇‘jar’模式,而且它更省存儲空間,但是驗證md5時比'raw'模式耗時()
        dexMode = "jar"
        //需要處理dex路徑,支持*、?通配符,必須使用'/'分割。路徑是相對安裝包的,例如/assets/...
        pattern = ["classes*.dex", "assets/secondary-dex-?.jar"]
        //它定義了哪些類在加載補丁包的時候會用到。這些類是通過Tinker無法修改的類,也是一定要放在main dex的類。
        loader = ["com.tencent.tinker.loader.*", "com.cn21.tinkertest.MyApplication"]
        /**
         * 這里需要定義的類有:
         1. 你自己定義的Application類;
         2. Tinker庫中用于加載補丁包的部分類,即com.tencent.tinker.loader.*;
         3. 如果你自定義了TinkerLoader,需要將它以及它引用的所有類也加入loader中;
         4. 其他一些你不希望被更改的類,例如Sample中的BaseBuildInfo類。這里需要注意的是,
         這些類的直接引用類也需要加入到loader中?;蛘吣阈枰獙⑦@個類變成非preverify。
         */
    }
    //lib相關(guān)的配置項
    lib {
        //需要處理lib路徑,支持*、?通配符,必須使用'/'分割。與dex.pattern一致, 路徑是相對安裝包的,例如/assets/...
        pattern = ["lib/armeabi/*.so", "lib/arm64-v8a/*.so", "lib/armeabi-v7a/*.so", "lib/mips/*.so", "lib/mips64/*.so", "lib/x86/*.so", "lib/x86_64/*.so"]
    }
    //res相關(guān)的配置項
    res {
        pattern = ["res/*", "assets/*", "resources.arsc", "AndroidManifest.xml"]
        //對于修改的資源,如果大于largeModSize,我們將使用bsdiff算法。
        // 這可以降低補丁包的大小,但是會增加合成時的復(fù)雜度。默認大小為100kb
        largeModSize = 100
    }
    //7zip路徑配置項,執(zhí)行前提是useSign為true
    sevenZip {
        //例如"com.tencent.mm:SevenZip:1.1.10",將自動根據(jù)機器屬性獲得對應(yīng)的7za運行文件,推薦使用。
        zipArtifact = "com.tencent.mm:SevenZip:1.1.10"
    }
}

上面只是使用了部分tinker參數(shù),全部參數(shù)及含義可參考tinkerPatch gradle參數(shù)官方wiki

2. 修改Application類

  1. 修改工程的Application類,使其繼承自DefaultApplicationLike,然后生成默認的構(gòu)造方法,并覆蓋onBaseContextAttached方法,然后添加一個registerActivityLifecycleCallbacks方法,同時在自己的Application類上加上以下注解:
 @DefaultLifeCycle(application = "com.cn21.tinkertest.MyApplication",
        flags = ShareConstants.TINKER_ENABLE_ALL,
        loadVerifyFlag = false)

其中,

  • application屬性指定的是tinker為我們生成的真正的Application,一般是包名+自定義的Application名稱作為名字,其中application屬性指定的是tinker為我們生成的真正的Application類,需要注意兩點,一是AndroidManifest.xml 中的application節(jié)點下的name 屬性必須是這個application屬性的值。As找不到這個Application報錯但不會影響編譯成功;二是在app/build.gradle文件中的tinkerPatch-dex-loader節(jié)點中添加application屬性的值(見tinkerPatch gradle配置)。
  • flags屬性指定tinker可以修復(fù)的范圍,TINKER_ENABLE_ALL是全部都可以修復(fù),還有TINKER_DEX_AND_LIBRARYTINKER_RESOURCE_MASK,TINKER_DEX_MASK等等,根據(jù)名字就可以知道所代表的含義。

以下是完整的自定義Application代碼:

@DefaultLifeCycle(application = "com.cn21.tinkertest.MyApplication",
        flags = ShareConstants.TINKER_ENABLE_ALL,
        loadVerifyFlag = false)
public class TinkerTestApplicarion extends DefaultApplicationLike {

    public TinkerTestApplicarion(Application application, int tinkerFlags, boolean tinkerLoadVerifyFlag, long applicationStartElapsedTime, long applicationStartMillisTime, Intent tinkerResultIntent) {
        super(application, tinkerFlags, tinkerLoadVerifyFlag, applicationStartElapsedTime, applicationStartMillisTime, tinkerResultIntent);
    }

    /**
     *  install tinker
     * @param base
     */
    @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
    @Override
    public void onBaseContextAttached(Context base) {
        super.onBaseContextAttached(base);
        TinkerInstaller.install(this);
    }

    @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
    public void registerActivityLifecycleCallbacks(Application.ActivityLifecycleCallbacks callback) {
        getApplication().registerActivityLifecycleCallbacks(callback);
    }
}

3. 使用tinker生成補丁

到此,配置已經(jīng)基本完成了。下面開始使用。

  1. 首先編譯運行一次工程,將生成的apk保存?zhèn)浞菰诔薭uild/output/apk以外的文件夾,tinker會讀取這個舊的apk與新的apk進行比較生成補丁,同時需要修改app/build.gradle文件中oldApk的路徑。
  2. 修改工程中代碼或者資源,然后打開As gradle任務(wù)欄,找到tinker任務(wù)那一項,選擇對應(yīng)的tinker任務(wù)運行
enter description here
enter description here

然后在build/outputs/tinkerPatch目錄下會生成補丁包與相關(guān)日志。將補丁包patch_signed_7zip.apkpush到手機的sdcard目錄,此時就可以在工程需要的地方調(diào)用tinker 的補丁加載方法了

TinkerInstaller.onReceiveUpgradePatch(getApplicationContext(), Environment.getExternalStorageDirectory().getAbsolutePath() + "/patch_signed_7zip.apk");

因為需要讀取sdcard中的文件,因此讀寫權(quán)限必須要配置。
如果補丁加載成功,可以在logcat中看到以下信息

enter description here
enter description here

需要注意的是,tinker默認補丁成功后會殺死應(yīng)用,因此如果有需要則自定義ResultService繼承自DefaultTinkerResultService,修改補丁成功后的行為

  1. 重啟應(yīng)用則可以看到打補丁后的效果。

更詳盡的tinker知識請參考tinker Github主頁,包括tinker源碼與使用示例都可以看到

Tinker接入其他問題

1. 開啟multidex支持

如果項目需要用到multidex則需要在gradle中添加multidex依賴,

 compile "com.android.support:multidex:1.0.1"

在android-defaultConfig節(jié)點中添加

 multiDexEnabled true

在Application初始化tinker之前加入

 MultiDex.install(base);

2. 多渠道打包

tinker默認是每個渠道生成一個對應(yīng)的補丁包,這樣子會造成空間浪費和發(fā)布的時候容易出錯。因此官方推薦使用packer-ng-plugin工具進行多渠道打包。

3. 資源混淆

如果應(yīng)用使用了AndResGuard混淆資源文件,編譯流程需要做特殊處理,具體請參考這篇文章

4. 應(yīng)用加固

tinker1.7.6之后不再支持加固

5.tinker與instant run的兼容問題

事實上,若編譯時都使用assemble*, tinker與instant run是可以兼容的。但是不少用戶基礎(chǔ)包與補丁包混用兩種模式導(dǎo)致補丁過大,所以tinker編譯時禁用instant run,我們可以在設(shè)置中禁用instant run或使用assemble方式編譯。

大家日常debug時若想開啟instant run功能,可以將tinker暫時關(guān)閉:

ext {
    //for some reason, you may want to ignore tinkerBuild, such as instant run debug build?
    tinkerEnabled = false
}

更多常見問題請參見官方wiki

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容