熱更新之Bugly框架的詳細整合

isJoker發表於2017-12-21

對於廣大移動開發者而言,App的版本更新升級是再尋常不過的事。但是當你發現你剛發出去的包有緊急Bug需要修復時,你就不淡定了,又要經過繁瑣的傳統的App版本更新流程,重新釋出一個修復Bug的版本,再將Apk上傳到各大應用商店,使用者需要花費時間去應用商店重新下載安裝。如果Bug比較嚴重,有些使用者可能會失去耐心,直接解除安裝掉App,於是乎,你們的使用者就這樣流失了。


傳統的更新流程有幾個弊端,一是重新發布版本費時費力代價高,二是使用者安裝需要去應用商店下載成本高,三是不能及時的修復bug,使用者體驗差。但是H5的出現使使這種情況有了小小的轉機,把需要經常變更的業務邏輯以H5的方式獨立出來,再嵌入App中。為什麼說是小小的轉機,因為App中仍有原生的程式碼,你不能保證原生的程式碼不會出Bug。於是乎,熱更新就應運而生。


目前國內的熱更新技術有很多,比如阿里的AndFix、Sophix;微信的Tinker;QQ空間的超級補丁;餓了嗎的Amigo;美團的Robust等,各有優缺點。實現程式碼修復主要有兩大方案:阿里系的底層替換和騰訊系的類載入。底層替換限制比較多,但能立即載入補丁包實現熱修復。類載入需要冷啟動才能使熱修復生效,但限制少,修復的範圍廣。有興趣的同學都可以去了解下。推薦一本阿里的工程師出的熱修復書籍《深入探索Android熱修復技術原理》,內容詳細的講解了熱修復原理。今天我主要教大家如何將騰訊的基於Tinker的熱修復框架Bugly整合到專案中,接下來開始講整合的步驟。


第一步:新增外掛依賴 工程根目錄下“build.gradle”檔案中新增:

buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        // tinkersupport外掛, 其中latest.release指拉取最新版本,也可以指定明確版本號,例如1.0.4
        classpath "com.tencent.bugly:tinker-support: latest.release"
    }
}
複製程式碼

第二步:整合SDK gradle配置 在app module的“build.gradle”檔案中新增(示例配置):

android {
        defaultConfig {
        }
      }
      dependencies {
          compile "com.android.support:multidex:1.0.1" // 多dex配置
          //註釋掉原有bugly的倉庫
          //compile 'com.tencent.bugly:crashreport:latest.release'
          //其中latest.release指代最新版本號,也可以指定明確的版本號,例如2.3.2
          compile 'com.tencent.bugly:crashreport_upgrade:1.3.4'
      }
複製程式碼

在app module的“build.gradle”檔案中新增:

// 依賴外掛指令碼
apply from: 'tinker-support.gradle'
複製程式碼

tinker-support.gradle內容如下所示(示例配置): 注:您需要在同級目錄下建立tinker-support.gradle這個檔案哦。

apply plugin: 'com.tencent.bugly.tinker-support'

def bakPath = file("${buildDir}/bakApk/")

/**
 * 此處填寫每次構建生成的基準包目錄
 */
def baseApkDir = "app-0208-15-10-00"

/**
 * 對於外掛各引數的詳細解析請參考
 */
tinkerSupport {

    // 開啟tinker-support外掛,預設值true
    enable = true

    // 指定歸檔目錄,預設值當前module的子目錄tinker
    autoBackupApkDir = "${bakPath}"

    // 是否啟用覆蓋tinkerPatch配置功能,預設值false
    // 開啟後tinkerPatch配置不生效,即無需新增tinkerPatch
    overrideTinkerPatchConfiguration = true

    // 編譯補丁包時,必需指定基線版本的apk,預設值為空
    // 如果為空,則表示不是進行補丁包的編譯
    // @{link tinkerPatch.oldApk }
    baseApk = "${bakPath}/${baseApkDir}/app-release.apk"

    // 對應tinker外掛applyMapping
    baseApkProguardMapping = "${bakPath}/${baseApkDir}/app-release-mapping.txt"

    // 對應tinker外掛applyResourceMapping
    baseApkResourceMapping = "${bakPath}/${baseApkDir}/app-release-R.txt"

    // 構建基準包和補丁包都要指定不同的tinkerId,並且必須保證唯一性
    tinkerId = "1.0.0-base"

    // 構建多渠道補丁時使用
    // buildAllFlavorsDir = "${bakPath}/${baseApkDir}"
    // 是否啟用加固模式,預設為false.(tinker-spport 1.0.7起支援)
    // isProtectedApp = true

    // 是否開啟反射Application模式
enableProxyApplication = false
supportHotpugComponent = true
}

/**
 * 一般來說,我們無需對下面的引數做任何的修改
 * 對於各引數的詳細介紹請參考:
 * https://github.com/Tencent/tinker/wiki/Tinker-%E6%8E%A5%E5%85%A5%E6%8C%87%E5%8D%97
 */
tinkerPatch {
    //oldApk ="${bakPath}/${appName}/app-release.apk"
    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
        //tinkerId = "1.0.1-base"
        //applyMapping = "${bakPath}/${appName}/app-release-mapping.txt" //  可選,設定mapping檔案,建議保持舊apk的proguard混淆方式
        //applyResourceMapping = "${bakPath}/${appName}/app-release-R.txt" // 可選,設定R.txt檔案,通過舊apk檔案保持ResId的分配
    }
}
複製程式碼

第三步:初始化SDK enableProxyApplication = false 的情況 這是Tinker推薦的接入方式,一定程度上會增加接入成本,但具有更好的相容性。 整合Bugly升級SDK之後,我們需要按照以下方式自定義ApplicationLike來實現Application的程式碼(以下是示例): 自定義Application

public class MyApplication extends TinkerApplication {
    public MyApplication() {
        super(ShareConstants.TINKER_ENABLE_ALL, "xxx.xxx.MyApplicationLike",
                "com.tencent.tinker.loader.TinkerLoader", false);
    }
}
複製程式碼

別忘了將MyApplication 加入到AndroidMenifest中的application標籤下的name屬性中。

自定義ApplicationLike

public class MyApplicationLike extends DefaultApplicationLike {
    public MyApplicationLike(Application application, int tinkerFlags,
            boolean tinkerLoadVerifyFlag, long applicationStartElapsedTime,
            long applicationStartMillisTime, Intent tinkerResultIntent) {
        super(application, tinkerFlags, tinkerLoadVerifyFlag, applicationStartElapsedTime, applicationStartMillisTime, tinkerResultIntent);
    }


    @Override
    public void onCreate() {
        super.onCreate();
        // 這裡實現SDK初始化,appId替換成你的在Bugly平臺申請的appId
        // 除錯時,將第三個引數改為true
        Bugly.init(getApplication(), "fb2bada5b4", true);
    }


    @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
    @Override
    public void onBaseContextAttached(Context base) {
        super.onBaseContextAttached(base);
        // you must install multiDex whatever tinker is installed!
        MultiDex.install(base);

        // 安裝tinker
        // TinkerManager.installTinker(this); 替換成下面Bugly提供的方法
        Beta.installTinker(this);
    }

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

}
複製程式碼

enableProxyApplication = true 時直接在你的Application的onCreate()方法中呼叫 Bugly.init(getApplication(), "fb2bada5b4", true);


第四步:AndroidManifest.xml配置 在AndroidMainfest.xml中進行以下配置: 許可權配置

<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.READ_LOGS" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
複製程式碼

第五步:混淆配置 為了避免混淆SDK,在Proguard混淆檔案中增加以下配置:

-dontwarn com.tencent.bugly.**
-keep public class com.tencent.bugly.**{*;}
複製程式碼

如果你使用了support-v4包,你還需要配置以下混淆規則:


-keep class android.support.**{*;}
複製程式碼

到此為止,已經成功的整合了Bugly框架


接下來測試熱修復


1、編譯基準包 配置基準包的tinkerId

這裡寫圖片描述

執行assembleRelease編譯生成基準包:

這裡寫圖片描述

這個會在build/bakApk路徑下生成每次編譯的基準包、混淆配置檔案、資源Id檔案,如下圖所示:

這裡寫圖片描述

啟動上一步生成的app-release.apk,啟動後,SDK會自動上報聯網資料 我們每次冷啟動都會請求補丁策略,會上報當前版本號和tinkerId,這樣我們後臺就能將這個唯一的tinkerId對應到一個版本。


2、對基線版本的bug修復 未修復前

這裡寫圖片描述

修復後

這裡寫圖片描述


3、根據基線版本生成補丁包 修改待修復apk路徑、mapping檔案路徑、resId檔案路徑和tinkerId

這裡寫圖片描述

執行構建補丁包的task

這裡寫圖片描述

生成的補丁包在build/outputs/patch目錄下:

這裡寫圖片描述


4、上傳補丁包到平臺 上傳補丁包到平臺並下發編輯規則

這裡寫圖片描述

這裡寫圖片描述

選擇檔案後會自動識別目標版本,若出現“未匹配到可用補丁的App版本”,如果你的基線版本沒有上報過聯網,基於這個版本生成的補丁包就無法匹配到,啟動之前生成的app-release.apk,啟動後,SDK會自動上報聯網資料


5、測試補丁應用效果 啟動基線版本App,點選顯示文字時崩潰,只是我們前面造的一個空指標異常,重新啟動基線版本App,等待一兩分鐘後,會開始自動下載補丁包,下載成功之後會立即合成補丁,我配置了Beta.canNotifyUserRestart = true ,所以會彈出對話方塊,提醒使用者更新應用。由於Tinker需要再次冷啟動才能使補丁生效,點選重啟應用後會退出,再重新啟動apk,點選顯示文字,文字內容顯示為修復後的內容,之前的的空指標異常就被成功修復了。

這裡寫圖片描述

這裡寫圖片描述

這裡寫圖片描述


Bugly還支援全量升級、異常上報、執行統計,詳情可以看Bugly官方文件:https://bugly.qq.com/docs/ 本文demo: https://github.com/isJoker/WanBuglyHotFixDemo

相關文章