熱修復——Bugly讓熱修復變得如此簡單

一、簡述

在上一篇《熱修復——Tinker的集成與使用》中,根據Tinker官方Wiki集成了Tinker,但那僅僅只是本地集成,有一個重要的問題沒有解決,那就是補丁從服務器下發到用戶手機上,如果你團隊中的後臺開發人員實力夠強,那麼完全可以自己做一個補丁管理系統,但我想應該沒多少人願意花精力在這個後臺管理系統的開發上面吧,且開發有時候就是在造bug,鬼知道會挖出一個多大的坑呢?對於這樣的一個問題,據我所知,市面上有3種Tinker的補丁管理系統,如下:

「Bugly」和「tinker-manager」是免費的,「tinkerpatch」是收費的,因爲「tinkerpatch」收費,所以暫時不做考慮。Bugly由騰訊團隊開發並維護,穩定性肯定沒得說,而「tinker-manager」是GitHub上個人開發者開發維護的,穩定性沒法保證(我沒有貶低開發者的意思,畢竟勢單力薄,人多力量大嘛),故本人覺得,Bugly是目前最優的Tinker熱修復解決方案。在開始進入Bugly集成之前,你可以先點擊下載Demo試試看看。

二、獲取App ID

要使用Bugly的熱修復功能,首先得註冊並登錄Bugly,然後點擊進入「Bugly產品頁面」,或點擊“我的產品 ”。

我這個賬號之前是沒有創建過產品,所以這裏什麼也沒有,接着點擊“新建產品”。

填寫必要的信息後,點擊“保存”。

通過“產品設置”,選擇剛剛創建的產品(圖中第3步),可以查看到產品對應的App ID。

這個App ID很重要,先記錄好,後續會用到。

Demo的App ID爲: 3062edb401。不要用我的,對你來說一點用處都沒有,請使用你自己產品的App ID。

二、添加插件依賴

項目的build.gradle:

dependencies {
    classpath 'com.android.tools.build:gradle:3.0.0'
    // tinkersupport插件(1.0.3以上無須再配置tinker插件)
    classpath "com.tencent.bugly:tinker-support:1.1.1"
}

三、集成SDK

app的build.gradle:

apply from: 'tinker-support.gradle'
android {
    defaultConfig {
        ...
        // 開啓multidex
        multiDexEnabled true
    }
    // recommend
    dexOptions {
        jumboMode = true
    }
    // 簽名配置
    signingConfigs {
        release {
            try {
                storeFile file("./keystore/release.keystore")
                storePassword "testres"
                keyAlias "testres"
                keyPassword "testres"
            } catch (ex) {
                throw new InvalidUserDataException(ex.toString())
            }
        }

        debug {
            storeFile file("./keystore/debug.keystore")
        }
    }

    // 構建類型
    buildTypes {
        release {
            minifyEnabled true
            signingConfig signingConfigs.release
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
        debug {
            debuggable true
            minifyEnabled false
            signingConfig signingConfigs.debug
        }
    }

    sourceSets {
        main {
            jniLibs.srcDirs = ['libs']
        }
    }
}

dependencies {
    ...
    implementation "com.android.support:multidex:1.0.1" // 多dex配置
    implementation 'com.tencent.bugly:crashreport_upgrade:1.3.4'// 遠程倉庫集成方式(推薦)
}

簽名配置部分請根據你項目的實際情況修改,如:

四、配置Tinker

在app的build.gradle文件同級目錄下創建一個tinker-support.gradle文件,內容如下:

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

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

/**
 * 此處填寫每次構建生成的基準包目錄
 */
def baseApkDir = "tinker-bugly-1211-16-01-34"
def myTinkerId = "base-" + rootProject.ext.android.versionName // 用於生成基準包(不用修改)
//def myTinkerId = "patch-" + rootProject.ext.android.versionName + ".0.0" // 用於生成補丁包(每次生成補丁包都要修改一次,最好是 patch-${versionName}.x.x)

/**
 * 對於插件各參數的詳細解析請參考
 */
tinkerSupport {

    // 開啓tinker-support插件,默認值true
    enable = true

    // 是否啓用加固模式,默認爲false.(tinker-spport 1.0.7起支持)
    // isProtectedApp = true

    // 是否開啓反射Application模式
    enableProxyApplication = true

    // 是否支持新增非export的Activity(注意:設置爲true才能修改AndroidManifest文件)
    supportHotplugComponent = 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 = "${myTinkerId}"

    // 構建多渠道補丁時使用
    // buildAllFlavorsDir = "${bakPath}/${baseApkDir}"

}

/**
 * 一般來說,我們無需對下面的參數做任何的修改
 * 對於各參數的詳細介紹請參考:
 * https://github.com/Tencent/tinker/wiki/Tinker-%E6%8E%A5%E5%85%A5%E6%8C%87%E5%8D%97
 */
tinkerPatch {
    ...
}

1、overrideTinkerPatchConfiguration

當overrideTinkerPatchConfiguration = true時,tinkerPatch可以省略不寫,Bugly會加載默認的Tinker配置。但請注意,如果你的so文件不是存放在libs目錄下(與src目錄同級),又或者資源文件的存放在你自定義的目錄中,那麼這時你要小心了,這些文件在製作補丁包時不會被檢測,也就是說這些so文件和資源文件將不會被熱修復,這種情況下就需要將overrideTinkerPatchConfiguration = false,並設置tinkerPatch的lib和res屬性。

其它具體的配置與說明可以查看「Tinker-接入指南」

2、baseApkDir

baseApkDir是基準包(也稱基線包)的目錄,在生產補丁時需要根據基準包在bakApk下具體文件夾名字修改,如:bakApk/xxxx,到時生成補丁包時要將baseApkDir的值改爲xxxx。(xxxx是Tinker自動生成的,根據時間戳來命名)。

3、tinkerId

tinkerId是Bugly熱修復方案最最重要的一個因素,一般取值爲git版本號、versionName等等(我習慣用versionName),它會將補丁包與基準包產生對應關係,假設基準包的tinkerId爲 base-1.0,則生成的補丁包中的YAPATCH.MF文件關係如下:

Bugly要求baseApk(基準包)的tinkerId與補丁包的tinkerId要不一樣。所以,在生成基準包時,請用如下tinkerId:

def myTinkerId = "base-" + rootProject.ext.android.versionName // 用於生成基準包(不用修改)

當生成補丁包時,請使用如下tinkerId:

def myTinkerId = "patch-" + rootProject.ext.android.versionName + ".0.0" // 用於生成補丁包(每次生成補丁包都要修改一次,最好是 patch-${versionName}.x.x)

對於同一個基準包,我們可能會多次生成補丁包上傳到Bugly的熱修復管理後臺,這時,這些補丁包的tinkerId也要不一樣,不然的話,當客戶手機上的App在獲取補丁時,會錯亂(親測,當同個基準包的補丁包的tinkerId一樣時,App每次重啓都會獲取不同的補丁包,導致tinkerId相同的補丁包輪流下發)。所以,”patch-” + rootProject.ext.android.versionName + “.0.0”中的”.0.0”(稱爲計數)就是爲了區分每次生成的補丁包,如.0.1,.0.2等等,建議versionName更新時計數重置。

因爲Tinker的配置放在了tinker-support.gradle文件中,與app的build.gradle不在同一個文件中,所以沒辦法通過android.defaultConfig.versionName直接獲取App的versionName,這裏我使用了config.gradle來提取共同的屬性,rootProject.ext.android.versionName獲取的是config.gradle中的versionName屬性,詳情請百度。

4、補丁新舊判定

def myTinkerId = "patch-" + rootProject.ext.android.versionName + ".0.0" // 用於生成補丁包(每次生成補丁包都要修改一次,最好是 patch-${versionName}.x.x)

對於一個基準包,可以在Bugly上發佈多個補丁包(切記tinkerid不同),這裏或許會讓你誤以爲計數越大,表明補丁越新,這是錯誤的,這個計數僅僅只是區分不同的補丁包而已,它沒有標記補丁新舊的作用,補丁新舊由Bugly來判定,最後上傳的補丁便是最新的補丁,舉個例子,我在昨天上傳了tinkerid爲”patch-1.0.0.9”的補丁1,在今天上傳了tinkerid爲”patch-1.0.0.1”的補丁2,雖然補丁2的計數比補丁1小,但補丁2比補丁1晚上傳,所以補丁2是最新的補丁,即補丁新舊與計數無關。Bugly會下發並應用最新的補丁(即補丁2),但還是建議計數從小到大計算,這裏僅僅只是說明Bugly如何判定補丁新舊罷了。

五、初始化SDK

Bugly的初始化工作需要在Application中完成,但對原生Tinker來說,默認的Application是無法實現熱修復的。看過Tinker官方Wiki的人應該知道,Tinker針對Application無法熱修復的問題,給予開發者兩個選擇,分別是:

  • 使用「繼承TinkerApplication + DefaultApplicationLike」。
  • 使用「DefaultLifeCycle註解 + DefaultApplicationLike」。

這2種選擇都需要對自定義的Application進行改造,對於自定義Application代碼不多的情況來說還可以接受,但有些情況還是比較”討厭”這2種選擇的,對此,Bugly給出了它的2種解決方法,分別如下:

  • 使用原來的自定義Application,Bugly通過反射爲App動態生成新的Application。
  • 使用「繼承TinkerApplication + DefaultApplicationLike」。

DefaultLifeCycle註解在Bugly中被閹割了。

分別對應tinker-support.gradle文件中enableProxyApplication的值:true或false。

1、enableProxyApplication = true

Bugly將通過反射的方式針對項目中自定義的Application動態生成新的Application,下圖是源碼中的AndroidManifest.xml和編譯好的apk中的AndroidManifest.xml:

既然將enableProxyApplication的值設置爲true,那接下來的重點就是完成Bugly的初始化工作了。需要在自定義的Application的onCreate()中進行Bugly的配置,在attachBaseContext()中進行Bugly的安裝:

public class MyApplication extends Application {

    private Context mContext;

    @Override
    public void onCreate() {
        super.onCreate();
        mContext = getApplicationContext();
        // 這裏實現SDK初始化,appId替換成你的在Bugly平臺申請的appId
        // 調試時,將第三個參數改爲true
        configTinker();
    }

    @Override
    protected void attachBaseContext(Context base) {
        super.attachBaseContext(base);
        // you must install multiDex whatever tinker is installed!
        MultiDex.install(mContext);
        // 安裝tinker
        // 此接口僅用於反射Application方式接入。
        Beta.installTinker();
    }

}

注意:
1. Bugly的安裝必須在attachBaseContext()方法中,否則將無法從Bugly服務器獲取最新補丁。
2. tinker需要你開啓MultiDex,你需要在dependencies中進行配置compile “com.android.support:multidex:1.0.1”纔可以使用MultiDex.install方法。

最後在清單文件中,聲明使用我們自定義的Application即可:

<application
    android:name="com.lqr.MyApplication"
    ...>

2、enableProxyApplication = false

這是Bugly推薦的方式,穩定性有保障(因爲第1種方式使用的是反射,可能會存在不穩定的因素),它需要對Application進行改造,首先就是繼承TinkerApplication,然後在默認的構造函數中,將第2個參數修改爲你項目中的ApplicationLike繼承類的全限定名稱:

public class SampleApplication extends TinkerApplication {
    public SampleApplication() {
        super(ShareConstants.TINKER_ENABLE_ALL, "com.lqr.SampleApplicationLike",
                "com.tencent.tinker.loader.TinkerLoader", false);
    }
}

注意:這個類集成TinkerApplication類,這裏面不做任何操作,所有Application的代碼都會放到ApplicationLike繼承類當中
參數解析
參數1:tinkerFlags 表示Tinker支持的類型 dex only、library only or all suuport,default: TINKER_ENABLE_ALL
參數2:delegateClassName Application代理類 這裏填寫你自定義的ApplicationLike
參數3:loaderClassName Tinker的加載器,使用默認即可
參數4:tinkerLoadVerifyFlag 加載dex或者lib是否驗證md5,默認爲false

接着就是創建ApplicationLike繼承類:

public class SampleApplicationLike extends DefaultApplicationLike {

    public static final String TAG = "Tinker.SampleApplicationLike";
    private Application mContext;

    public SampleApplicationLike(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();
        mContext = getApplication();
        configTinker();
    }

    @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
        Beta.installTinker(this);
    }

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

    @Override
    public void onTerminate() {
        super.onTerminate();
        Beta.unInit();
    }
}

注意:
SampleApplicationLike這個類是Application的代理類,以前所有在Application的實現必須要全部拷貝到這裏,在onCreate方法調用SDK的初始化方法,在onBaseContextAttached中調用Beta.installTinker(this)。

最後在清單文件中,聲明改造好的Application(注意不是ApplicationLike):

<application
    android:name="com.lqr.SampleApplication"
    ...>

3、配置Bugly

這是Bugly官方給出的配置,應有盡有,註釋也很nice,請仔細看看,對項目的功能拓展與用戶體驗有幫助:

private void configTinker() {
    // 設置是否開啓熱更新能力,默認爲true
    Beta.enableHotfix = true;
    // 設置是否自動下載補丁,默認爲true
    Beta.canAutoDownloadPatch = true;
    // 設置是否自動合成補丁,默認爲true
    Beta.canAutoPatch = true;
    // 設置是否提示用戶重啓,默認爲false
    Beta.canNotifyUserRestart = true;
    // 補丁回調接口
    Beta.betaPatchListener = new BetaPatchListener() {
        @Override
        public void onPatchReceived(String patchFile) {
            Toast.makeText(mContext, "補丁下載地址" + patchFile, Toast.LENGTH_SHORT).show();
        }

        @Override
        public void onDownloadReceived(long savedLength, long totalLength) {
            Toast.makeText(mContext,
                    String.format(Locale.getDefault(), "%s %d%%",
                            Beta.strNotificationDownloading,
                            (int) (totalLength == 0 ? 0 : savedLength * 100 / totalLength)),
                    Toast.LENGTH_SHORT).show();
        }

        @Override
        public void onDownloadSuccess(String msg) {
            Toast.makeText(mContext, "補丁下載成功", Toast.LENGTH_SHORT).show();
        }

        @Override
        public void onDownloadFailure(String msg) {
            Toast.makeText(mContext, "補丁下載失敗", Toast.LENGTH_SHORT).show();
        }

        @Override
        public void onApplySuccess(String msg) {
            Toast.makeText(mContext, "補丁應用成功", Toast.LENGTH_SHORT).show();
        }

        @Override
        public void onApplyFailure(String msg) {
            Toast.makeText(mContext, "補丁應用失敗", Toast.LENGTH_SHORT).show();
        }

        @Override
        public void onPatchRollback() {

        }
    };

    // 設置開發設備,默認爲false,上傳補丁如果下發範圍指定爲“開發設備”,需要調用此接口來標識開發設備
    Bugly.setIsDevelopmentDevice(mContext, false);
    // 多渠道需求塞入
    // String channel = WalleChannelReader.getChannel(getApplication());
    // Bugly.setAppChannel(getApplication(), channel);
    // 這裏實現SDK初始化,appId替換成你的在Bugly平臺申請的appId
    Bugly.init(mContext, "e9d0b7f57f", true);
}

這裏就用到了一開始獲取到的App ID了,將其傳入Bugly.init()方法的第二個參數,切記,用你自己的App ID。

其中如下兩個方法很重要:

  • Bugly.setIsDevelopmentDevice()

設置當前設備是不是開發設備,這跟Bugly上傳補丁包時所選的”下發範圍”有關。

  • Bugly.init(context, appid, isDebug)

這個方法除了設置App ID外,還可以設置是否輸出Log,可以觀察到Bugly在App啓動時做了哪些聯網操作。

六、AndroidManifest.xml

1、 權限配置

<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"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>

2、Activity配置

<activity
    android:name="com.tencent.bugly.beta.ui.BetaActivity"
    android:configChanges="keyboardHidden|orientation|screenSize|locale"
    android:theme="@android:style/Theme.Translucent"/>

3、FileProvider配置

<provider
    android:name="android.support.v4.content.FileProvider"
    android:authorities="${applicationId}.fileProvider"
    android:exported="false"
    android:grantUriPermissions="true">
    <meta-data
        android:name="android.support.FILE_PROVIDER_PATHS"
        android:resource="@xml/provider_paths"/>
</provider>

如果你使用的第三方庫也配置了同樣的FileProvider, 可以通過繼承FileProvider類來解決合併衝突的問題,示例如下:

<provider
    android:name=".utils.BuglyFileProvider"
    android:authorities="${applicationId}.fileProvider"
    android:exported="false"
    android:grantUriPermissions="true"
    tools:replace="name,authorities,exported,grantUriPermissions">
    <meta-data
        android:name="android.support.FILE_PROVIDER_PATHS"
        android:resource="@xml/provider_paths"
        tools:replace="name,resource"/>
</provider>

4、升級SDK下載路徑配置

在res目錄新建xml文件夾,創建provider_paths.xml文件如下:

<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <!-- /storage/emulated/0/Download/${applicationId}/.beta/apk-->
    <external-path name="beta_external_path" path="Download/"/>
    <!--/storage/emulated/0/Android/data/${applicationId}/files/apk/-->
    <external-path name="beta_external_files_path" path="Android/data/"/>
</paths>

注:1.3.1及以上版本,可以不用進行以上配置,aar已經在AndroidManifest配置了,並且包含了對應的資源文件。

七、混淆

# Bugly混淆規則
-dontwarn com.tencent.bugly.**
-keep public class com.tencent.bugly.**{*;}

# 避免影響升級功能,需要keep住support包的類
-keep class android.support.**{*;}

好了,集成完畢,接下來就是製作基準包、補丁包和上傳補丁包了。

八、製作基準包

在app編碼完成並測試完成後,就是打包上線了,上線前打的包就是基準包啦,下面我們就來製作基準包,分3步:

  1. 打開app下的tinker-support.gradle文件。
  2. 將帶”base”的tinkerId註釋解開,並註釋掉帶”patch”的tinkerId。
  3. 雙擊運行build下的assembleRelease。

通常主Module的名字是”app”,但我這個Demo是”tinker-bugly”,所以你執行第3步時,要根據具體項目找到要製作基準包的主Module。

AS在執行assembleRelease指令時,就是在編譯基準包了,當編譯完成時,app的build目錄下會自動生成基準包文件夾,以時間戳來命名的(也就是說,每次執行assembleRelease指令都會在build目錄創建不同的基準包文件夾)。

這3個文件對之後製作補丁包來說是相當重要的,你需要做的就是將這3個文件保存好,可以保存到雲盤、Git服務器上等等,但就不要讓它就這麼放着,因爲在你執行clean Project時,app的build目錄會被刪除,這樣基準包及mapping與R文件都會丟失。

到這裏,你就可以把它(基準包:tinker-bugly-release.apk)上架到應用市場了。試下Demo:

tip:加固與多渠道打包

本篇不涉及具體的加固與多渠道打包。

1、加固

如果你的app需要加固,那就需要在製作基準包之前,將tinker-support.gradle文件的isProtectedApp = true的註釋去掉,然後加固,重新簽名,最後上架,它對加固平臺也有一定的要求。

詳情見「Bugly熱更新使用範例文檔最後:加固打包」部分。

2、多渠道打包

分「gradle配置productFlavors方式」與「多渠道打包工具打多渠道包方式(推薦)」。

詳情見「Bugly熱更新使用範例文檔:多渠道打包」部分。

九、補丁包

現在要動態修復App了,對於代碼修復、so庫修復、資源文件修復,分別對應Demo中的”say something”、”get string from .so”、”我的頭像”,修復過程無非是改代碼,替換so文件,替換資源文件,這裏就不演示了,直接開始製作補丁包,先將tinker-support.gradle文件打開。

1、基準包命名

確保基準包及相關文件的命名與配置文件中的一致:

2、修改baseApkDir與tinkerId

  1. 修改baseApkDir的值爲基準包所有文件夾的名字。
  2. 註釋掉帶”base”的tinkerId,取消帶”patch”的tinkerId的註釋(多次生成補丁時,記得修改”計數”,區分不同的補丁)。

3、執行編譯,生成補丁

打開側邊的Gradle標籤,找到項目的主Module,雙擊tinker-support下的buildTinkerPatchRelease指令,生成補丁包。

當編譯完成後,在app的build/outputs/patch目錄下會在”patch_singed_7zip.apk”文件,它就是補丁包,雙擊打開它,可以看到其中有一個YAPATCH.MF,裏面記錄了基準包與補丁包的tinkerId(兩者是肯定不同,如果一樣則說明配置有問題了)。

十、上傳補丁包

1、流程圖解

首先,點擊進入「Bugly產品頁面」,或點擊“我的產品 ”查看我的產品。

點擊你要管理的產品後,依次點擊”應用升級”、”熱更新”,可以查看到該產品的補丁下發情況(這個產品我還沒上傳過補丁,故一片空白)。

按下圖順序操作即可上傳補丁包:

2、上傳失敗分析

有可能你在上傳完補丁包時,頁面會提示”未匹配到可應用補丁包的App版本,請確認補丁包的基線版本是否已經發布”。

遇到這種情況請先冷靜,首先來說明一件事:Bugly怎麼知道基線版本是否已經發布?

通常按我們理解的,基準包發佈就是上架到應用市場,但應用市場又不會通知Bugly某某產品已經上架了,對吧。其實,Bugly的上架通知是這樣的:當基準包在手機上啓動時,Bugly框架就會讓App聯網通知Bugly的服務器,同時上傳當前App的版本號、tinkerId等信息,它這麼做的目的有如下兩個:

  • 標記某個tinkerId的基準包已經被安裝到手機上使用了(即發佈)。
  • 獲取該tinkerId的基準包最新的補丁信息。

所以,當出現了”未匹配到可應用補丁包的App版本,請確認補丁包的基線版本是否已經發布”這樣的提示時,可以確定,這個基準包的tinkerId等信息沒有被上傳到Bugly服務器,對此,鄙人將踩過的坑總結起來,摸索出了自己的解決方法,分如下幾步:

  • 檢查App是否能夠聯網。
  • 檢查App ID是否正確。
  • 結合enableProxyApplication的取值,檢查AndroidManifest.xml中聲明的Application是否寫對。
  • 檢查Bugly的安裝是不是在attachBaseContext()或onBaseContextAttached()方法中完成。

像我就犯過這樣的錯,明明在tinker-support.gradle文件中設置了enableProxyApplication = true,結果在AndroidManifest.xml中卻聲明瞭TinkerApplication的繼承類。

所以這裏只需要將AndroidManifest.xml中聲明我們自定義的Application即可(MyApplication)。

除了聯網問題以外,其他的幾種情況都需要重新生成基準包。這裏再分享一個可以快速確定App是否有上傳過版本信息的方法:

3、上傳成功

先驗證下上面的方法,當我把問題解決掉之後,把重新生成的基準包安裝到手機上打開(此時Bugly框架會上傳App的版本號、tinkerId到服務器),再查看”版本管理”,出現了,版本號爲”1.0”(其實就是App的versionName)。

再回頭來看看上傳補丁,這次又會有什麼不同呢?

耶,成功。點擊”立即下發”,可以看到現在補丁處於”下發中”狀態:

隨便來看看用戶手中的App是什麼反應吧(真正將補丁下發到用戶手機上的這段時間可能會有點久,不是立即下發的):

再回頭看看Bugly服務器上的補丁下發情況:

十一、其他

1、補丁管理

Bugly服務器除了可以上傳下發補丁外,還可以對補丁進行管理:

  1. 停止下發:不再把該補丁下發到客戶手機上(停止後可重新開啓)。
  2. 撤回:將Bugly服務器上的某個補丁刪掉,這個操作是不可逆的(不知道用戶手機上被成功打上的補丁是否也會被卸載)。
  3. 編輯:可以修改”下發範圍”(開發設備、全量設備、備註等等)。
  4. 歷史:查看修改記錄。

2、強調一下一些需要注意的地方

  1. 一個基準包可以有多個補丁包,Bugly會將最新的補丁進行下發(舊補丁默認會變成”停止下發狀態”),客戶手機上的App的舊補丁會被新補丁覆蓋。
  2. 製作基礎包時,請使用帶”base”的tinkerId,執行的是assembleRelease指令。
  3. 製作基礎包後,一定要將baseApk、mapping.txt、R.txt保存好,不能弄丟了。
  4. 製作補丁包時,先將baseApkDir的值修改爲基準包所有文件夾的名字,然後啓用帶”patch”的tinkerId,同時修改”計數”,執行的是buildTinkerPatchRelease指令。
  5. 製作補丁包後,最後打開它檢查YAPATCH.MF文件中的from和to信息,檢查該補丁包對應的基準包的tinkerId是否正確。
  6. 建議上線的基準包將Bugly的Log輸出關閉:Bugly.init(mContext, AppID, false);
  7. 如果是測試補丁包是否用效果,建議設置爲開發設備:Bugly.setIsDevelopmentDevice(mContext, true);
  8. so文件需要手動先調用一下 TinkerLoadLibrary.installNavitveLibraryABI(this, CPU_ABI) 方法才能生效。

3、Bugly官方文檔

4、本系列文章鏈接

最後貼下Demo鏈接

https://github.com/GitLqr/HotFixDemo

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章