天天看點

騰訊Tinker 熱修複 Andriod studio 3.0 配置和內建(三)Bugly內建騰訊Tinker 熱修複 Andriod studio 3.0 Bugly內建和多管道更新檔管理釋出

騰訊Tinker 熱修複 Andriod studio 3.0 Bugly內建和多管道更新檔管理釋出

本文說明

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

  • 騰訊Tinker 熱修複 Andriod studio 30 Bugly內建和多管道更新檔管理釋出
    • 開始
      • 答疑
      • 介紹
      • 為什麼使用Bugly熱更新
      • 視訊教程
      • 本文demo
    • Bugly配置內建
      • 添加插件依賴
      • 內建SDK
        • gradle配置
        • 配置依賴插件腳本tinker-supportgradle
        • TinkerSupport插件使用指南
        • 配置分包多管道插件multiple-channelgradle
      • 初始化SDK
        • Tinker推薦配置模式enableProxyApplication false 的情況
        • Tinker推薦配置模式enableProxyApplication true的情況
      • 混淆配置
    • Bugly 內建後的使用
      • 生成baseApk
      • 生成tinker更新檔包
    • Bugly平台使用
      • 上傳更新檔
      • 上傳更新檔包遇到的問題
    • 關于碰到的問題
    • Bugly Android 熱更新常見問題
      • 更多
    • Bugly多管道熱更新解決方案
    • 往期文章

開始

答疑

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

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 
    defaultConfig {
        applicationId "com.achers.asbuglytinker"
        minSdkVersion 
        targetSdkVersion 
        versionCode 
        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 = 
    }

    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 ==  ?  : savedLength *  / 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包,你還需要配置以下混淆規則:

Bugly 內建後的使用

生成baseApk

看過前兩篇關于Tinker 這種基本操作就很簡單了,差不多一毛一樣。
           
騰訊Tinker 熱修複 Andriod studio 3.0 配置和內建(三)Bugly內建騰訊Tinker 熱修複 Andriod studio 3.0 Bugly內建和多管道更新檔管理釋出
檢視生成目錄
           
騰訊Tinker 熱修複 Andriod studio 3.0 配置和內建(三)Bugly內建騰訊Tinker 熱修複 Andriod studio 3.0 Bugly內建和多管道更新檔管理釋出

生成tinker更新檔包

這裡和tinker不同 tinker是執行  tinker包下的 而bugly是執行tinker-support
           
騰訊Tinker 熱修複 Andriod studio 3.0 配置和內建(三)Bugly內建騰訊Tinker 熱修複 Andriod studio 3.0 Bugly內建和多管道更新檔管理釋出
生成更新檔包的同時會生成新的baseApk檔案夾 這個就是在oldApk(有bug)基礎上更新代碼後執行tinker-support NewApk(修複版)上圖
           
騰訊Tinker 熱修複 Andriod studio 3.0 配置和內建(三)Bugly內建騰訊Tinker 熱修複 Andriod studio 3.0 Bugly內建和多管道更新檔管理釋出

Bugly平台使用

騰訊Tinker 熱修複 Andriod studio 3.0 配置和內建(三)Bugly內建騰訊Tinker 熱修複 Andriod studio 3.0 Bugly內建和多管道更新檔管理釋出

上傳更新檔

選擇項目檔案下的7zip   注意不是thinkePatch下的apk 而是 patch下的
           
騰訊Tinker 熱修複 Andriod studio 3.0 配置和內建(三)Bugly內建騰訊Tinker 熱修複 Andriod studio 3.0 Bugly內建和多管道更新檔管理釋出

上傳更新檔包遇到的問題

騰訊Tinker 熱修複 Andriod studio 3.0 配置和內建(三)Bugly內建騰訊Tinker 熱修複 Andriod studio 3.0 Bugly內建和多管道更新檔管理釋出
原因是因為我們的baseApk雖然內建了bugly 但是沒有在運作就是沒有聯網與bugly對接 平台沒有記錄,是以找不到。解決辦法 運作下baseApk 就可以。
           

關于碰到的問題

tinkerid的設定
           
騰訊Tinker 熱修複 Andriod studio 3.0 配置和內建(三)Bugly內建騰訊Tinker 熱修複 Andriod studio 3.0 Bugly內建和多管道更新檔管理釋出
這裡官方是預設為true的 它會自己生成tinkerid 格式是versionName+versionCode+生成時間,他的意思很明顯就是你不配置tinkerid 就是按這個格式來配置,你就不用自己每次改一變tinkerid 但是非常難看 
比如 1.01TinkerId2018-14-11-20;看起來很難管理
因為Bugly與tinker不同的地方在與他會在更新檔包中增加一個檔案YAPATCH.MF用來記錄更新檔
           
騰訊Tinker 熱修複 Andriod studio 3.0 配置和內建(三)Bugly內建騰訊Tinker 熱修複 Andriod studio 3.0 Bugly內建和多管道更新檔管理釋出
這是我将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 多管道打包和釋出更新檔方式推薦

繼續閱讀