1. 程式人生 > >騰訊Tinker 熱修復 Andriod studio 3.0 配置和整合(三)Bugly整合

騰訊Tinker 熱修復 Andriod studio 3.0 配置和整合(三)Bugly整合

騰訊Tinker 熱修復 Andriod studio 3.0 Bugly整合和多渠道補丁管理髮布

本文說明
上一篇我說完了騰訊Tinker 熱修復之多渠道打包,這篇我們來初步瞭解下騰訊Tinker和Bugly結合來做熱修復多渠道補丁管理和整合。(其實在上週我已經整合測試完了demo也已經上傳了,就是突然想不到怎麼寫這篇,想了很久這篇不會寫得很多,因為官方給出了視訊啊,視訊詳細得多了,我主要分享下我所在整合中碰到的問題)

開始

答疑

在公司做技術分享的時候,我老大提出了幾個問題,我當時沒有回答出來,因為確實我沒看得很深入(比如原始碼層,檔案生成目的等)這裡我重新看了下官方文件解析下

1.生成baseApk資料夾中的mapping.txt是什麼 有什麼用? R檔案呢?
mapping.txt其實就是apk的混淆後的程式碼文字 R檔案也是一樣 防止反編譯

2.baseApk 每次都會生成怎麼管理?
baseApk對應的是上一次的oldApk,第一次是baseApk是為空的,建議自己創檔案儲存,根據檔名日期去管理,而補丁包也是一樣。

3.TinkerSupport 外掛中 tinkerPatch配置 有什麼用?
它其實就是全域性資訊相關的配置項,對應overrideTinkerPatchConfiguration 預設flase 不啟用使用預設的配置 開啟可以自定義配置。

介紹

熱更新能力是Bugly為解決開發者緊急修復線上bug,而無需重新發版讓使用者無感知就能把問題修復的一項能力。Bugly目前採用微信Tinker的開源方案,開發者只需要整合我們提供的SDK就可以實現自動下載補丁包、合成、並應用補丁的功能,我們也提供了熱更新管理後臺讓開發者對每個版本補丁進行管理

為什麼使用Bugly熱更新?

  • 無需關注Tinker是如何合成補丁的
  • 無需自己搭建補丁管理後臺
  • 無需考慮後臺下發補丁策略的任何事情
  • 無需考慮補丁下載合成的時機,處理後臺下發的策略
  • 提供了更加方便整合Tinker的方式
  • 通過HTTPS及簽名校驗等機制保障補丁下發的安全性
  • 豐富的下發維度控制,有效控制補丁影響範圍
  • 提供了應用升級一站式解決方案

視訊教程

2017年3月的有參考意義

視訊地址

本文demo

本文最新Bugly熱修復整合包括多渠道demo

Bugly配置整合

新增外掛依賴

andriod studio 3.0 配置

工程根目錄下“build.gradle”檔案中新增:
    classpath 'com.android.tools.build:gradle:3.0.1'


        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
        // tinkersupport外掛, 其中lastest.release指拉取最新版本,也可以指定明確版本號,例如1.0.9
       // classpath "com.tencent.bugly:tinker-support:lastest.release"
        classpath "com.tencent.bugly:tinker-support:1.1.1"//tinker版本至1.9.1 對應bugly 1.1.1 看更新文件
        // 多渠道外掛(多渠道打包推薦使用)
        classpath 'com.meituan.android.walle:plugin:1.1.3'
    }

整合SDK

gradle配置


在app module的“build.gradle”檔案中新增(示例配置):
apply plugin: 'com.android.application'

android {
    signingConfigs {
        debug {
            storeFile file('./keystore/debug.keystore')
        }

        release {
            keyAlias 'buglyrelease'
            keyPassword '123456'
            storeFile file('D:/Users/Achers/AsBuglyTinker/app/keystore/buglyrelease.jks')
            storePassword '123456'
        }
    }
    compileSdkVersion 26
    defaultConfig {
        applicationId "com.achers.asbuglytinker"
        minSdkVersion 15
        targetSdkVersion 26
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"

        // 開啟multidex
        multiDexEnabled true
    }
    // 編譯選項
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_7
        targetCompatibility JavaVersion.VERSION_1_7
    }
    // recommend
    dexOptions {
        jumboMode = true
    }
    // 構建型別
    buildTypes {
        release {
            minifyEnabled true
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
            signingConfig signingConfigs.release
        }
        debug {
            debuggable true
            minifyEnabled false
            signingConfig signingConfigs.debug
        }
    }
    sourceSets {
        main {
            jniLibs.srcDirs = ['libs']
        }
    }
    repositories {
        flatDir {
            dirs 'libs'
        }
    }
    // 多渠道配置
    /*productFlavors {
         xiaomi {

         }
         yyb {

         }
     }*/
    lintOptions {
        checkReleaseBuilds false
        abortOnError false
    }




}

dependencies {
    implementation fileTree(include: ['*.jar'], dir: 'libs')
    implementation 'com.android.support:appcompat-v7:26.1.0'
    implementation 'com.android.support.constraint:constraint-layout:1.0.2'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.1'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1'
    // 多dex配置
    implementation 'com.android.support:multidex:1.0.1'
    // 整合Bugly熱更新aar(本地整合使用方式)
    //      compile(name: 'bugly_crashreport_upgrade-1.3.2', ext: 'aar')
    // 遠端倉庫整合方式(推薦)
    implementation 'com.tencent.bugly:crashreport_upgrade:1.3.4'
    // walle(多渠道使用)
     compile 'com.meituan.android.walle:library:1.1.3'
}


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

// 多渠道使用walle示例(注:多渠道使用)
apply from: 'multiple-channel.gradle'

配置依賴外掛指令碼tinker-support.gradle

在app下新建tinker-support.gradle


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

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

/**
 * 此處填寫每次構建生成的基準包目錄
 */
def baseApkDir = "app-0114-18-17-58"

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

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

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

    autoGenerateTinkerId = false

    // 是否啟用覆蓋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 = "patch-1.0.2"

//    buildAllFlavorsDir = "${bakPath}/${baseApkDir}"
    // 是否開啟加固模式,預設為false
    // isProtectedApp = true

    //預設為false 就是需要自己改造application  為true 就是不需要改造 通過反射
    enableProxyApplication = false

    // 是否支援新增非export的Activity(注意:設定為true才能修改AndroidManifest檔案)
    supportHotplugComponent = 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-patch"
       // applyMapping = "${bakPath}/${appName}/app-release-mapping.txt" //  可選,設定mapping檔案,建議保持舊apk的proguard混淆方式
        //applyResourceMapping = "${bakPath}/${appName}/app-release-R.txt" // 可選,設定R.txt檔案,通過舊apk檔案保持ResId的分配
    }

}

TinkerSupport外掛使用指南

引數介紹地址TinkerSupport外掛使用指南

配置分包多渠道外掛multiple-channel.gradle

在app下新建multiple-channel.gradle

apply plugin: 'walle'
walle {
    // 指定渠道包的輸出路徑
    apkOutputFolder = new File("${project.buildDir}/outputs/channels");
    // 定製渠道包的APK的檔名稱
    apkFileNameFormat = '${appName}-${packageName}-${channel}-${buildType}-v${versionName}-${versionCode}-${buildTime}.apk';
    // 渠道配置檔案
    channelFile = new File("${project.getProjectDir()}/channel")
}

初始化SDK

Tinker推薦配置模式enableProxyApplication = false 的情況

自定義Application

/**
 * Create on 2018/1/14 12:23
 * <p>
 * author lhm
 * <p>
 * Description:  enableProxyApplication = false 的情況
 * 這是Tinker推薦的接入方式,一定程度上會增加接入成本,但具有更好的相容性。
 * <p>
 * Version: 1.2.3
 *
 * 注意:這個類整合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
 */
public class APP extends TinkerApplication {
    public APP() {
        super(ShareConstants.TINKER_ENABLE_ALL, "com.achers.asbuglytinker.SampleApplicationLike",
                "com.tencent.tinker.loader.TinkerLoader", false);
    }

}

清單檔案配置 application


    <application
        android:name=".APP"
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

SampleApplicationLike 配置

package com.achers.asbuglytinker;

import android.annotation.TargetApi;
import android.app.Application;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.support.multidex.MultiDex;
import android.widget.Toast;

import com.meituan.android.walle.WalleChannelReader;
import com.tencent.bugly.Bugly;
import com.tencent.bugly.beta.Beta;
import com.tencent.bugly.beta.interfaces.BetaPatchListener;
import com.tencent.tinker.loader.app.DefaultApplicationLike;

import java.util.Locale;

/**
 * Create on 2018/1/14 12:26
 * <p>
 * author lhm
 * <p>
 * Description:
 * <p>
 * Version: 1.2.3
 */
public class SampleApplicationLike  extends DefaultApplicationLike {

    public static final String TAG = "Tinker.SampleApplicationLike";

    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();
        // 設定是否開啟熱更新能力,預設為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(getApplication(), "補丁下載地址" + patchFile, Toast.LENGTH_SHORT).show();
            }

            @Override
            public void onDownloadReceived(long savedLength, long totalLength) {
                Toast.makeText(getApplication(),
                        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(getApplication(), "補丁下載成功", Toast.LENGTH_SHORT).show();
            }

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

            }

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

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

            @Override
            public void onPatchRollback() {

            }
        };

        // 設定開發裝置,預設為false,上傳補丁如果下發範圍指定為“開發裝置”,需要呼叫此介面來標識開發裝置
        Bugly.setIsDevelopmentDevice(getApplication(), true);
        // 多渠道需求塞入
         String channel = WalleChannelReader.getChannel(getApplication());
         Bugly.setAppChannel(getApplication(), channel);
        // 這裡實現SDK初始化,appId替換成你的在Bugly平臺申請的appId
        Bugly.init(getApplication(), "4694167734", 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);

        // TODO: 安裝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();
    }

}

Tinker推薦配置模式enableProxyApplication = true的情況

無須你改造Application,主要是為了降低接入成本,我們外掛會動態替換AndroidMinifest檔案中的Application為我們定義好用於反射真實Application的類(需要您接入SDK 1.2.2版本 和 外掛版本 1.0.3以上)。
public class MyApplication extends Application {

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

    @Override
    protected void attachBaseContext(Context base) {
        super.attachBaseContext(base);
        // you must install multiDex whatever tinker is installed!
        MultiDex.install(base);


        // 安裝tinker
        Beta.installTinker();
    }

}

混淆配置

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

-dontwarn com.tencent.bugly.**
-keep public class com.tencent.bugly.**{*;}

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


-keep class android.support.**{*;}

Bugly 整合後的使用

生成baseApk

 看過前兩篇關於Tinker 這種基本操作就很簡單了,差不多一毛一樣。

生成baseApk

檢視生成目錄

baseApk目錄

生成tinker補丁包

這裡和tinker不同 tinker是執行  tinker包下的 而bugly是執行tinker-support

生成tinker補丁包

生成補丁包的同時會生成新的baseApk資料夾 這個就是在oldApk(有bug)基礎上更新程式碼後執行tinker-support NewApk(修復版)上圖

包

Bugly平臺使用

Bugly平臺

上傳補丁

選擇專案檔案下的7zip   注意不是thinkePatch下的apk 而是 patch下的

這裡寫圖片描述

上傳補丁包遇到的問題

這裡寫圖片描述

原因是因為我們的baseApk雖然集成了bugly 但是沒有在執行就是沒有聯網與bugly對接 平臺沒有記錄,所以找不到。解決辦法 執行下baseApk 就可以。

關於碰到的問題

tinkerid的設定

這裡寫圖片描述

這裡官方是預設為true的 它會自己生成tinkerid 格式是versionName+versionCode+生成時間,他的意思很明顯就是你不配置tinkerid 就是按這個格式來配置,你就不用自己每次改一變tinkerid 但是非常難看 
比如 1.01TinkerId2018-14-11-20;看起來很難管理
因為Bugly與tinker不同的地方在與他會在補丁包中增加一個檔案YAPATCH.MF用來記錄補丁

這裡寫圖片描述

這是我將autoGenerateTinkerId=false 後 每次手動設定的tinkerid
tinkerid 規則 基礎包為 base-1.0.1 對應補丁包 patch-1.0.1
            基礎包為 base-1.0.2 對應補丁包 patch-1.0.2
            就是為了自己定義方便管理。

Bugly Android 熱更新常見問題

Q:之前使用Tinker怎麼切換過來使用Bugly?

A:Bugly使用原始碼整合Tinker,如果之前整合過Tinker,你需要註釋掉以下配置:

//    compile("com.tencent.tinker:tinker-android-lib:${TINKER_VERSION}") { changing = true }
//    provided("com.tencent.tinker:tinker-android-anno:${TINKER_VERSION}") { changing = true } 

外掛配置不需要更改,只需要加上我們Bugly額外的tinker-support外掛即可:

 classpath "com.tencent.bugly:tinker-support:latest.release"

Q: 基線版本表示什麼意思?

A:表示你需要修復apk的版本,比如你已經上線了某個版本的apk,你需要用一個唯一的tinkerId來標識這個版本,而補丁包也是基於這個版本打的。

Q:打一個補丁包需要改哪些東西?

A:

修復bug的類、修改資源
修改oldApk配置
修改tinkerId
Q:tinkerId該怎麼填?

A:在執行過程中,我們需要驗證基準apk包的tinkerId是否等於補丁包的tinkerId。這個是決定補丁包能執行在哪些基準包上面,一般來說我們可以使用git版本號、versionName等等。

Q:兩次傳入的tinkerId是否一樣?

A:不一樣的,編譯補丁包時,tinker會自動讀取基準包AndroidManifest的tinkerId作為package_meta.txt中的TINKER_ID。將本次編譯傳入的tinkerId,作為package_meta.txt的NEW_TINKER_ID。

Q. 為什麼我上傳補丁提示我“未匹配到可用補丁的App版本”?

A:如果你的基線版本沒有上報過聯網,基於這個版本生成的補丁包就無法匹配到,請檢查你的基線版本配置是否正確。(具體參考:啟動apk,上報聯網資料)

Q. 我該上傳哪個補丁?patch目錄跟tinkerPatch目錄下的補丁有什麼區別嗎?

A:你必須要上傳build/outputs/patch目錄下的補丁包,

Q:我以前的是Bugly SDK,現在整合升級SDK會有什麼影響?

A:不會有影響的,升級SDK內建Bugly功能模組,你只需要將初始化方法改為統一的Bugly.init(getApplicationContext(), “註冊時申請的APPID”, false);即可。

Q:你們是怎麼定義開發裝置的?

A:我們會提供介面Bugly.setIsDevelopmentDevice(getApplicationContext(), true);,我們後臺就會將你當前裝置識別為開發裝置,如果設定為false則非開發裝置,我們會根據這個配置進行策略控制。

Q:如果我配置了升級策略,又配置了補丁策略,會是怎樣的效果?

A:升級策略優先順序會高於補丁策略,後臺會優先下發升級策略。畢竟你都要升級了,熱更新只是幫助你修復bug而已。

Q:我只想使用熱更新,不想使用升級?

A:熱更新是包含在升級SDK裡面的,你可以不配置任何升級策略,只需按照熱更新文件整合即可。

Q:是否支援加固模式?

A:

Bugly 1.3.0及以上版本支援(tinker 1.7.9)

需要你在tinker-support配置中設定isProtectedApp = true。

更多

熱更新常見問題

Bugly多渠道熱更新解決方案

bugly多渠道就跟tinker一毛一樣了,這裡貼這些沒什麼意義大家請看官方文章

Bugly多渠道熱更新解決方案

往期文章

騰訊Tinker 熱修復 Andriod studio 3.0 配置和整合

騰訊Tinker 熱修復 Andriod studio 3.0 多渠道打包和釋出補丁方式推薦