1. 程式人生 > >Android Tinker熱修復

Android Tinker熱修復

應該都遇到過剛上線的app,突然發現一個嚴重的bug需要修復,怎麼辦,以前的做法修改bug,然後重新打包app,測試,發新版本。即使是僅僅改動一行程式碼,也需要這麼繁瑣的操作來修復bug。
後來,出現熱修復技術,使用者在使用過程中不知不覺bug就修復了。
現在已經出現了很多熱修復技術了,這裡記錄Tinker的整合接入,更加詳細的內容可以查閱官方文件

Tinker 是一個開源專案(Github連結),它是微信官方的 Android 熱補丁解決方案,它支援動態下發程式碼、So 庫以及資源,讓應用能夠在不需要重新安裝的情況下實現更新。
這裡寫圖片描述

Tinker SDK接入
第一步 新增 gradle 外掛依賴,在專案的builder.gradle裡面新增TinkerPatch 外掛

buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        // TinkerPatch 外掛
        classpath "com.tinkerpatch.sdk:tinkerpatch-gradle-plugin:1.1.7"
    }
}

第二步 整合 TinkerPatch SDK
在app的gradle檔案app/build.gradle中

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

第三步 配置 tinkerpatchSupport 引數
在app目錄下,建立tinkerpatch.gradle
這裡寫圖片描述

為了簡單方便,我們將 TinkerPatch 相關的配置都放於 tinkerpatch.gradle 中, 我們需要在app的gradle檔案app/build.gradle中還新增:

apply from
: 'tinkerpatch.gradle'

開啟引入的 tinkerpatch.gradle 檔案,它的具體引數如下:

apply plugin: 'tinkerpatch-support'

/**
 * TODO: 請按自己的需求修改為適應自己工程的引數
 */
def bakPath = file("${buildDir}/bakApk/")
def baseInfo = "app-1.0.0-0803-17-03-11"// 注意!!!  改成對應的路徑
def variantName = "release"

/**
 * 對於外掛各引數的詳細解析請參考
 * http://tinkerpatch.com/Docs/SDK
 */
tinkerpatchSupport {
    /** 可以在debug的時候關閉 tinkerPatch **/
    tinkerEnable = true
    reflectApplication = true

    autoBackupApkPath = "${bakPath}"

    appKey = "1fb8b4539cbc48db"// 注意!!!  需要修改成你的appkey

    /** 注意: 若釋出新的全量包, 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或者檔案方式生成渠道資訊(相關工具:walle 或者 packer-ng)
     **/
}

/**
 * 用於使用者在程式碼中判斷tinkerPatch是否被使能
 */
android {
    defaultConfig {
        buildConfigField "boolean", "TINKER_ENABLE", "${tinkerpatchSupport.tinkerEnable}"
    }
}
/**
 * 一般來說,我們無需對下面的引數做任何的修改
 * 對於各引數的詳細介紹請參考:
 * 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
    }
}

將以上程式碼完整複製到tinkerpatch.gradle中
引數說明
bakPath:基包路徑
baseInfo:基包資料夾名(打補丁包的時候,需要修改)
這裡寫圖片描述
appKey:進入官網註冊一個賬號,新增APP,得到對應的appKey。這裡寫圖片描述

第四步 初始化 TinkerPatch SDK
1. reflectApplication = true 的情況
若我們使用 reflectApplication 模式,我們無需為接入 Tinker 而改造我們的 Application 類。

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章節中的,初始化SDK
            TinkerPatch.init(tinkerApplicationLike)
                    .reflectPatchLibrary()
                    .setPatchRollbackOnScreenOff(true)
                    .setPatchRestartOnSrceenOff(true);

            // 每隔3個小時去訪問後臺時候有更新,通過handler實現輪訓的效果
            new FetchPatchHandler().fetchPatchWithInterval(3);
            Log.i("TAG", "tinker init");
        }
    }
}
  1. reflectApplication = false 的情況
    若我們已經完成了應用的 Application 改造,即將 Application 的邏輯移動到 ApplicationLike類中
public class MyApplicationLike extends DefaultApplicationLike {

    ...

    @Override
    public void onCreate() {
        super.onCreate();
            if (BuildConfig.TINKER_ENABLE) {

            // 我們可以從這裡獲得Tinker載入過程的資訊
            tinkerApplicationLike = TinkerPatchApplicationLike.getTinkerPatchApplicationLike();

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

            // 每隔3個小時去訪問後臺時候有更新,通過handler實現輪訓的效果
            new FetchPatchHandler().fetchPatchWithInterval(3);
            Log.i("TAG", "tinker init");
        }
    }
}

建立FetchPatchHandler用於檢測是否更新

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

    /**
     * 通過handler, 達到按照時間間隔輪訓的效果
     * @param hour
     */
    public void fetchPatchWithInterval(int hour) {
        //設定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);
    }
}

最後在記得在Manifest.xml中配置Application,還要給SD卡讀寫許可權

<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>

第五步 使用步驟
打正式包,打包前先配置簽名
在app/build.gradle中,配置簽名

 //需要配置自己的簽名
    signingConfigs {
        release {
                keyAlias '******' //簽名別名
                keyPassword '******' //簽名密碼
                storeFile file('******')//簽名檔案.keystore或者.jks
                storePassword '******'//簽名密碼
        }
    }

    buildTypes {
        release {
            signingConfig signingConfigs.release
        }
    }

開啟Studio右側的Gradle,選擇assemableRelease打正式包
這裡寫圖片描述
完成後可以在資料夾build中找到生成的檔案(這裡稱為基包)
這裡寫圖片描述
開啟build -> bakApk -> app-1.0.0-0803-17-03-11(根據時間命名)
release資料夾中會出現我們剛打完的包。一個apk,對應一個txt檔案。
將apk安裝到手機上,這是一個剛建立的專案,裡面只有Hello World
這裡寫圖片描述

然後,修改程式碼,彈出一個這就是補丁…

Toast.makeText(this, "這是個補丁...", Toast.LENGTH_SHORT).show();

開啟tinkerpatch.gradle修改baseInfo成對應的檔名
這裡寫圖片描述
修改tinkerpatch.gradle中的tinkerpatchSupport -> appVersion
這裡寫圖片描述
完成後開啟Gradle,如下選擇tinkerPatchRelease
這裡寫圖片描述
補丁包將位於 build/outputs/tinkerPatch 中,這裡只需要用到patch_signed_7zip.apk
這裡寫圖片描述
最後把生成的補丁包,在Tinker官網網站釋出
這裡寫圖片描述
這裡寫圖片描述
版本號對應tinkerpatch.gradle中的appVersion
這裡寫圖片描述
選擇patch_signed_7zip.apk檔案,提交即可(更多下發選項,參考官方文件)

提交後,檢視補丁的下載數量以及成功應用數
這裡寫圖片描述
這裡補丁的成功率並不是100%,有時候應用下載了補丁,但是卻沒有更新,需要多試幾次,或者過會再試。我剛開始試的時候有時立刻就更新補丁了,有時試過好多次都沒更新,以為那裡出錯了,但是過會再試,又更新了。
這裡寫圖片描述

提示:
1、新增SD卡許可權,
2、下載補丁後,殺掉程序重新開啟,補丁才會生效
3、補丁非即時生效,需要等一會兒