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

1、簡述

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

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

2、獲取App ID

要使用Bugly的熱修復功能,首先得註冊並登陸Bugly,而後點擊進入「Bugly產品頁面」,或點擊「個人產品 」。github

我這個帳號以前是沒有建立過產品,因此這裏什麼也沒有,接着點擊「新建產品」。api

填寫必要的信息後,點擊「保存」。服務器

經過「產品設置」,選擇剛剛建立的產品(圖中第3步),能夠查看到產品對應的App ID。app

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

Demo的App ID爲: 3062edb401。不要用個人,對你來講一點用處都沒有,請使用你本身產品的App ID。ide

2、添加插件依賴

項目的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"
}
複製代碼

3、集成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'// 遠程倉庫集成方式(推薦)
}
複製代碼

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

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 {
    ...
}
複製代碼

一、overrideTinkerPatchConfiguration

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

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

二、baseApkDir

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

三、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屬性,詳情請百度。

四、補丁新舊斷定

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如何斷定補丁新舊罷了。

5、初始化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。

一、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"
    ...>
複製代碼

二、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"
    ...>
複製代碼

三、配置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啓動時作了哪些聯網操做。

6、AndroidManifest.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"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
複製代碼

二、Activity配置

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

三、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>
複製代碼

四、升級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配置了,而且包含了對應的資源文件。

7、混淆

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

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

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

8、製做基準包

在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:加固與多渠道打包

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

一、加固

若是你的app須要加固,那就須要在製做基準包以前,將tinker-support.gradle文件的isProtectedApp = true的註釋去掉,而後加固,從新簽名,最後上架,它對加固平臺也有必定的要求。

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

二、多渠道打包

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

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

9、補丁包

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

一、基準包命名

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

二、修改baseApkDir與tinkerId

  1. 修改baseApkDir的值爲基準包全部文件夾的名字。
  2. 註釋掉帶"base"的tinkerId,取消帶"patch"的tinkerId的註釋(屢次生成補丁時,記得修改"計數",區分不一樣的補丁)。

三、執行編譯,生成補丁

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

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

10、上傳補丁包

一、流程圖解

首先,點擊進入「Bugly產品頁面」,或點擊「個人產品 」查看個人產品。

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

按下圖順序操做便可上傳補丁包:

二、上傳失敗分析

有可能你在上傳完補丁包時,頁面會提示"未匹配到可應用補丁包的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是否有上傳過版本信息的方法:

三、上傳成功

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

再回頭來看看上傳補丁,此次又會有什麼不一樣呢?

耶,成功。點擊"當即下發",能夠看到如今補丁處於"下發中"狀態:

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

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

11、其餘

一、補丁管理

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

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

二、強調一下一些須要注意的地方

  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) 方法才能生效。

三、Bugly官方文檔

四、本系列文章連接

最後貼下Demo連接

github.com/GitLqr/HotF…

相關文章
相關標籤/搜索