【Android】熱修復(fù)——Tinker(入門)

前言

不知你是否遇到這樣的情況?千辛萬苦上開發(fā)了一個版本,好不容易上線了,突然發(fā)現(xiàn)了一個嚴(yán)重bug需要進(jìn)行緊急修復(fù),怎么辦?難道又要重新打包App、測試,發(fā)布新個版本?就為了修改一兩行的代碼?
莫慌,這種問題其實可以分分鐘解決。如果你學(xué)會了這項黑科技——熱修復(fù)。
在用戶使用App的時候,不知不覺,這個Bug就被修復(fù)了。


莫慌

熱修復(fù):熱修復(fù)(也稱熱補丁、熱修復(fù)補丁,英語:hotfix)是一種包含信息的獨立的累積更新包,通常表現(xiàn)為一個或多個文件。這被用來解決軟件產(chǎn)品的問題(例如一個程序錯誤)?!S基百科

本文介紹了Tinker的接入方式,更加詳細(xì)的內(nèi)容可以查閱官方文檔

介紹

Tinker是微信官方的Android熱補丁解決方案,它支持動態(tài)下發(fā)代碼、So庫以及資源,讓應(yīng)用能夠在不需要重新安裝的情況下實現(xiàn)更新。當(dāng)然,你也可以使用Tinker來更新你的插件。
Tinker所支持的功能如下


來自官方Github

Tinker熱補丁方案·不僅支持類、So以及資源的替換,它還是2.X-7.X的全平臺支持。

接入

進(jìn)入正題吧

額,我是說進(jìn)入正題

在項目的build.gradle

buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        // TinkerPatch 插件
        classpath "com.tinkerpatch.sdk:tinkerpatch-gradle-plugin:1.1.4"
    }
}

然后在appgradle文件app/build.gradle

dependencies {
    // 若使用annotation需要單獨引用,對于tinker的其他庫都無需再引用
    provided("com.tencent.tinker:tinker-android-anno:1.7.7")
    compile("com.tinkerpatch.sdk:tinkerpatch-android-sdk:1.1.4")
}

在app目錄下,創(chuàng)建tinkerpatch.gradle(可以去后面的鏈接下載源碼,把這個文件拷進(jìn)去)

tinkerpatch.gradle

將 TinkerPatch 相關(guān)的配置都放于tinkerpatch.gradle中,然后在app的gradle文件app/build.gradle中還添加

apply from: 'tinkerpatch.gradle'

完整的app/buidl.gradle:

apply plugin: 'com.android.application'
apply from: 'tinkerpatch.gradle'
android {
    ...
}
dependencies {
    .....
    // 若使用annotation需要單獨引用,對于tinker的其他庫都無需再引用
    provided("com.tencent.tinker:tinker-android-anno:1.7.7")
    compile("com.tinkerpatch.sdk:tinkerpatch-android-sdk:1.1.4")
}

配置參數(shù)

打開之前創(chuàng)建的tinkerpatch.gradle添加

apply plugin: 'tinkerpatch-support'

/**
 * TODO: 請按自己的需求修改為適應(yīng)自己工程的參數(shù)
 */
def bakPath = file("${buildDir}/bakApk/")
def baseInfo = "app-1.0.0-0330-20-37-31"
def variantName = "release"

/**
 * 對于插件各參數(shù)的詳細(xì)解析請參考
 * http://tinkerpatch.com/Docs/SDK
 */
tinkerpatchSupport {
    /** 可以在debug的時候關(guān)閉 tinkerPatch **/
    tinkerEnable = true
    reflectApplication = true

    autoBackupApkPath = "${bakPath}"

    appKey = "你的appkey"http:// 注意?。?!  需要修改成你的appkey

    /** 注意: 若發(fā)布新的全量包, appVersion一定要更新 **/
    appVersion = "1.0.0"

    def pathPrefix = "${bakPath}/${baseInfo}/${variantName}/"
    def name = "${project.name}-${variantName}"

    baseApkFile = "${pathPrefix}/${name}.apk"
    baseProguardMappingFile = "${pathPrefix}/${name}-mapping.txt"
    baseResourceRFile = "${pathPrefix}/${name}-R.txt"
    /**
     *  若有編譯多flavors需求, 可以參照: https://github.com/TinkerPatch/tinkerpatch-flavors-sample
     *  注意: 除非你不同的flavor代碼是不一樣的,不然建議采用zip comment或者文件方式生成渠道信息(相關(guān)工具:walle 或者 packer-ng)
     **/
}

/**
 * 用于用戶在代碼中判斷tinkerPatch是否被使能
 */
android {
    defaultConfig {
        buildConfigField "boolean", "TINKER_ENABLE", "${tinkerpatchSupport.tinkerEnable}"
    }
}
/**
 * 一般來說,我們無需對下面的參數(shù)做任何的修改
 * 對于各參數(shù)的詳細(xì)介紹請參考:
 * https://github.com/Tencent/tinker/wiki/Tinker-%E6%8E%A5%E5%85%A5%E6%8C%87%E5%8D%97
 */
tinkerPatch {
    ignoreWarning = false
    useSign = true
    dex {
        dexMode = "jar"
        pattern = ["classes*.dex"]
        loader = []
    }
    lib {
        pattern = ["lib/*/*.so"]
    }

    res {
        pattern = ["res/*", "r/*", "assets/*", "resources.arsc", "AndroidManifest.xml"]
        ignoreChange = []
        largeModSize = 100
    }
packageConfig {
    }
    sevenZip {
        zipArtifact = "com.tencent.mm:SevenZip:1.1.10"
//        path = "/usr/local/bin/7za"
    }
    buildConfig {
        keepDexApply = false
    }
}
  • 參數(shù)說明
  • bakPath:基包路徑
  • baseInfo:基包文件夾名(打補丁包的時候,需要修改)
  • appKey:進(jìn)入官網(wǎng)注冊一個賬號,新增APP,得到對應(yīng)的appKey。
    官方參數(shù)說明

其中appKey在新增的APP中可以看到

appKey

使用

創(chuàng)建FetchPatchHandler用于檢測是否更新(剛打開時會檢測一次)

public class FetchPatchHandler extends Handler {
    public static final long HOUR_INTERVAL = 3600 * 1000;
    private long checkInterval;

    /**
     * 通過handler, 達(dá)到按照時間間隔輪訓(xùn)的效果
     * @param hour
     */
    public void fetchPatchWithInterval(int hour) {
        //設(shè)置TinkerPatch的時間間隔
        TinkerPatch.with().setFetchPatchIntervalByHours(hour);
        checkInterval = hour * HOUR_INTERVAL;
        //立刻嘗試去訪問,檢查是否有更新
        sendEmptyMessage(0);
    }
    @Override
    public void handleMessage(Message msg) {
        super.handleMessage(msg);

        //這里使用false即可
        TinkerPatch.with().fetchPatchUpdate(false);
        //每隔一段時間都去訪問后臺, 增加10分鐘的buffer時間
        sendEmptyMessageDelayed(0, checkInterval + 10 * 60 * 1000);
    }
}

創(chuàng)建MyApplication初始化(reflectApplication = true 時)

public class MyApplication extends Application{
    private ApplicationLike tinkerApplicationLike;
    @Override
    public void onCreate() {
        super.onCreate();
        if (BuildConfig.TINKER_ENABLE) {

            // 我們可以從這里獲得Tinker加載過程的信息
            tinkerApplicationLike = TinkerPatchApplicationLike.getTinkerPatchApplicationLike();

            // 初始化TinkerPatch SDK, 更多配置可參照API章節(jié)中的,初始化SDK
            TinkerPatch.init(tinkerApplicationLike)
                    .reflectPatchLibrary()
                    .setPatchRollbackOnScreenOff(true)
                    .setPatchRestartOnSrceenOff(true);

            // 每隔3個小時去訪問后臺時候有更新,通過handler實現(xiàn)輪訓(xùn)的效果
            new FetchPatchHandler().fetchPatchWithInterval(3);
        }
    }
}

最后在記得在Manifest.xml中配置Application,還要給SD卡讀寫權(quán)限

<manifest >
    .....
    <uses-permission android:name="android.permission.INTERNET"/>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
    <application
       ...
       android:name=".MyApplication">
</manifest>

注意:初始化的代碼建議緊跟 super.onCreate(),并且所有進(jìn)程都需要初始化,已達(dá)到所有進(jìn)程都可以被 patch 的目的
如果你確定只想在主進(jìn)程中初始化 tinkerPatch,那也請至少在 :patch 進(jìn)程中初始化,否則會有嚴(yán)重的 crash 問題

打生產(chǎn)包

注意:打包前記得配置簽名??蓞⒖荚创a

每次開發(fā)完成后,開始打包。
打開Studio右側(cè)的Gradle,選擇assemableRelease打正式包

Gradle

完成后可以在文件夾build中找到生成的文件(這里稱為基包)

結(jié)果

打開build -> bakApk -> app-1.0.0-0330-21-40-52 (根據(jù)時間命名)
release文件夾中會出現(xiàn)我們剛打完的包。一個apk,對應(yīng)一個txt文件。
將apk安裝到手機上,這是一個剛創(chuàng)建的項目,里面只有Hello World

app-1.0.0-0330-21-40-52備份,打補丁包的時候需要用到。
多渠道打包時會根據(jù)渠道名分包,目錄結(jié)構(gòu)相似。

生成補丁包

這里模擬一個修補bug的場景,發(fā)一個熱補丁,彈一個Toast。
注意:在打生產(chǎn)包的代碼上做修改

Toast.makeText(this, "這是個補丁", Toast.LENGTH_SHORT).show();
  • 將之前的備份好的基包復(fù)制到build/bakApk下,打開tinkerpatch.gradle修改baseInfo成對應(yīng)的文件名

    修改baseInfo

  • 修改tinkerpatch.gradle中的tinkerpatchSupport -> appVersion

    appVersion

  • 完成后打開Gradle,如下選擇tinkerPatchRelease

    Gradle

    補丁包將位于 build/outputs/tinkerPatch 中,這里只需要用到patch_signed_7zip.apk
    補丁包

發(fā)布

最后,只需要將剛生成的補丁包發(fā)布,然后靜靜等待即可。

  • 選擇對應(yīng)的app,添加APP版本
    添加APP版本

    添加版本

    版本號對應(yīng)tinkerpatch.gradle中的appVersion
  • 選擇patch_signed_7zip.apk文件,提交即可(更多下發(fā)選項,參考官方文檔)
    發(fā)布補丁
  • 提交后,查看補丁的下載數(shù)量以及成功應(yīng)用數(shù)


    補丁詳情

這時候重新打開app,等待補丁下載。下載完成后關(guān)閉app,再次打開,查看結(jié)果。就這樣,整個熱修復(fù)的流程就完成了。
注意:一定要關(guān)閉后打開,熱修復(fù)才會生效。

結(jié)果

關(guān)于兼容多渠道包

關(guān)于渠道包的問題,若使用flavor編譯渠道包,會導(dǎo)致不同的渠道包由于BuildConfig變化導(dǎo)致classes.dex差異。這里建議的方式有:

  • 將渠道信息寫在AndroidManifest.xml或文件中,例如channel.ini;
  • 將渠道信息寫在apk文件的zip comment中,這種是建議方式,例如可以使用項目packer-ng-plugin或者可使用V2 Scheme的walle;
  • 若不同渠道存在功能上的差異,建議將差異部分放于單獨的dex或采用相同代碼不同配置方式實現(xiàn);

已通過Walle實現(xiàn)【Android】Walle多渠道打包&Tinker熱修復(fù)

Tinker已知的問題:

  1. Tinker不支持修改AndroidManifest.xml,Tinker不支持新增四大組件;
  2. 由于Google Play的開發(fā)者條款限制,不建議在GP渠道動態(tài)更新代碼;
  3. Android N上,補丁對應(yīng)用啟動時間有輕微的影響;
  4. 不支持部分三星android-21機型,加載補丁時會主動拋出TinkerRuntimeException:checkDexInstall failed;
  5. 由于各個廠商的加固實現(xiàn)并不一致,在1.7.6以及之后的版本,tinker不再支持加固的動態(tài)更新;
    (在官方——對加固的支持看到相關(guān)的內(nèi)容,說是支持多種加固,不過我沒試過 2017.7.3)
  6. 對于資源替換,不支持修改remoteView。例如transition動畫,notification icon以及桌面圖標(biāo)。

Tips:

  • dubug模式下,tinkerpatch.gradle —> tinkerpatchSupport—>tinkerEnable需要改為false
  • 添加SD卡權(quán)限,
  • 下載補丁后,殺掉進(jìn)程重新打開,補丁才會生效
  • 補丁非即時生效,需要等一會兒

源碼地址

Github

參考

官方文檔
接入指南

以上有錯誤之處,感謝指出

最后編輯于
?著作權(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)容