天天看點

Android 騰訊 Bugly 熱修複

依賴配置流程

Bugly 文檔中心

引入依賴

  1. 在項目的 build.gradle 中加入依賴
buildscript {
    repositories {
        jcenter()
        google()
    }
    dependencies {
	    //...
        classpath "com.tencent.bugly:tinker-support:1.1.5"
    }
}
           
  1. 在項目 app 中加入依賴 ( 之前引入過的 bugly 異常捕獲和應用更新的依賴可以直接注釋掉,下面依賴已經包含了)
compile 'com.android.support:multidex:1.0.0' //有了就不需要再加了
	compile 'com.tencent.bugly:crashreport_upgrade:1.3.6'
    // 指定tinker依賴版本(注:應用更新1.3.5版本起,不再内置tinker)
    compile 'com.tencent.tinker:tinker-android-lib:1.9.9'
    compile 'com.tencent.bugly:nativecrashreport:3.7.1'
           

tinker 配置

  1. 在 app 檔案夾的根目錄建立 tinker-support.gradle 檔案内容如下(建議直接複制)
apply plugin: 'com.tencent.bugly.tinker-support'

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

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

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

    // 開啟tinker-support插件,預設值true
    enable = true

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

    //建議設定true,使用者就不用再自己管理tinkerId的命名,插件會為每一次建構的base包自動生成唯一的tinkerId,預設命名規則是versionname.versioncode_時間戳
    //具體參考https://github.com/BuglyDevTeam/Bugly-Android-Demo/wiki/Tinker-ID%E8%AF%A5%E6%80%8E%E4%B9%88%E8%AE%BE%E7%BD%AE
//    autoGenerateTinkerId = true

    //tinkerId必須保證唯一性,如果兩個base包的tinkerid是一樣的,并且都聯網激活了,那麼後續更新檔上傳到背景的時候會出現比對錯誤
//    tinkerId = "if autoGenerateTinkerId=true ,no need set here"

    // 是否啟用覆寫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 = "v1.8.5_patch_1"

    // 建構多管道更新檔時使用
    // buildAllFlavorsDir = "${bakPath}/${baseApkDir}"

    // 是否啟用加強模式,預設為false.(tinker-spport 1.0.7起支援)
    // isProtectedApp = true

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

    // 是否支援新增非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-base"
        //applyMapping = "${bakPath}/${appName}/app-release-mapping.txt" //  可選,設定mapping檔案,建議保持舊apk的proguard混淆方式
        //applyResourceMapping = "${bakPath}/${appName}/app-release-R.txt" // 可選,設定R.txt檔案,通過舊apk檔案保持ResId的配置設定
    }
}
           
  1. 在 app 的 build.gradle 加入 apply from: ‘tinker-support.gradle’ 這個配置檔案
apply plugin: 'com.android.application'
apply from: 'tinker-support.gradle'
android {
    //...
}
           

項目配置

這裡我隻說一種可以改 Application 的入侵性比較弱一點的方式,但是相對沒那麼穩定:)

  1. Application 配置( Bugly 擷取 appId 這裡就不說怎麼弄的了吧),記得引用這個 Application
@Override
    public void onCreate() {
        super.onCreate();
        //熱修複
        initBetaHotfix();
    }
    @Override
    protected void attachBaseContext(Context base) {
        super.attachBaseContext(base);
        MultiDex.install(this);
    }
    //熱更新
    private void initBetaHotfix() {
        // 設定是否開啟熱更新能力,預設為true
        Beta.enableHotfix = true;
        // 設定是否自動下載下傳更新檔,預設為true
        Beta.canAutoDownloadPatch = true;
        // 設定是否自動合成更新檔,預設為true
        Beta.canAutoPatch = true;
        // 設定是否提示使用者重新開機,預設為false
        Beta.canNotifyUserRestart = false;
        // 安裝tinker
        Beta.installTinker();

        // 設定開發裝置,預設為false,上傳更新檔如果下發範圍指定為“開發裝置”,需要調用此接口來辨別開發裝置
        Bugly.setIsDevelopmentDevice(this, true);
        // 調試時,将第三個參數改為true
        Bugly.init(this, Config.BUGLY_ID, true);
        
        //異常捕獲
        CrashReport.initCrashReport(getApplicationContext(), Config.BUGLY_ID, false);
    }
           
  1. AndroidManifest.xml 檔案配置,不考慮 bugly 熱修複依賴版本太低問題,不引入 FileProvider 相關配置
<activity
     android:name="com.tencent.bugly.beta.ui.BetaActivity"
     android:configChanges="keyboardHidden|orientation|screenSize|locale"
     android:theme="@android:style/Theme.Translucent" />
           

添權重限

<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" />
           

配置到這裡就完成啦

----------------------------------------- 華麗的分割 -----------------------------------------

打包流程

打包流程主要分成兩個一個是打基包,一個是打更新檔包

打基包

指定一個版本的基包例如 v1.8.5 版本的基包

  1. 在之前配置好的 tinker-support.gradle 檔案下修改 tinkerId 例如: tinkerId = “v1.8.5_base” 注意:這裡的thinkerId 要保證在你之前發過的版本裡是唯一的,這樣我們的更新檔包才可以檢測比對到
  2. 點選左邊标簽 (Gradle)選擇 項目名 > :app > Tasks > build > assembleRelease 運作(注意:如果沒有啟用混淆或者沒有簽名會出錯,必須啟用混淆并且簽名)
    Android 騰訊 Bugly 熱修複
  3. 運作完成後,在 app > build > bakApk > app-xxxx-xx-xx-xx 裡面有三個檔案,分别為: tinker-support.gradle 配置裡設定的三個檔案名(如果沒有 app-release-mapping.txt 檔案的話有可能是你沒有啟用混淆,或者是你的混淆檔案有問題)
    Android 騰訊 Bugly 熱修複
    Android 騰訊 Bugly 熱修複
  4. 将 app-1024-16-15-32(命名規則 月日-小時-分-秒) 這個檔案夾存檔(因為後面你 clean 項目或者 rebuild 項目這個檔案會消失)後面我們打包更新檔包的時候需要用到

通過以上步驟就完成了基包的打包(直接将這個包發到各大應用商城,不說分管道打包)

打包更新檔包

  1. 先把你想要打更新檔的基封包件(上面的 app-1024-16-15-32 檔案夾)放到 app > build > bakApk 檔案夾下,如果沒有 bakApk 檔案夾就建立一個。
  2. copy 你基封包件夾名稱 eg: app-1024-16-15-32 然後黏貼到 tinker-support.gradle 配置檔案的 def baseApkDir = “基封包件夾名稱”
    Android 騰訊 Bugly 熱修複
  3. 在這個配置檔案修改 tinkerId (跟上面基類包一樣 需要唯一)規則你自己來定
    Android 騰訊 Bugly 熱修複
  4. 生成更新檔包,Gradle > 項目名 > app > Tasks > thinker_support > buildTinkerPatchRelease
    Android 騰訊 Bugly 熱修複
  5. 生成的更新檔包會在 app > bulid > outputs > patch > release 檔案夾中,有三個 apk 檔案這裡我們需要用到的是 _7zip.apk 的 apk 檔案
    Android 騰訊 Bugly 熱修複
  6. 檢查我們的更新檔包,輕按兩下 patch_signed_7zip.apk 檔案 會有一個 YAPATCH.MF 檔案,點選這個檔案就可以檢視你的更新檔包的一些資訊,他是通過比對到基包的 thinkId 去下發更新檔包的,下面 tinkerId 打錯了(懶得改)
    Android 騰訊 Bugly 熱修複
  7. 接下來就可以直接上 bugly 的官網上釋出更新檔了,如果你怕出錯你可以先發一個測試裝置的更新檔包,沒問題再發全量裝置的更新檔包。

注意點

  1. 項目需要簽名打包
  2. 項目需要引入混淆配置 并打開
  3. 保留每次生成基類包的 apk , mapping , R 檔案 還有 tinkerId (唯一)
  4. 每次生成 patch 更新檔包的時候要修改 熱修複配置檔案的 tinkerId (需要唯一)
  5. 出現 tinker_intermediates\values_backup notfoundFile 的時候 clean 一下項目 然後把你之前基封包件(例如 app-1024-16-15-32 檔案夾)放到 app > build > bakApk 檔案夾下,如果沒有 bakApk 檔案夾就建立一個。

混淆檔案問題

我們混淆檔案可能會出現問題造成 app-release-mapping.txt 檔案沒有生成,在這裡我直接給出一個比較萬能的簽名檔案(你的其他庫的混淆還有你自己混淆加再後面就行啦,bugly 的混淆我已經加到最後面了)

注意: 之前存在的一些庫有需要加入混淆的都需要加上不然打包出來的安裝包可能會報錯

-keep public class com.android.vending.licensing.ILicensingService    # 保持哪些類不被混淆
-keep public class com.google.vending.licensing.ILicensingService   # 保持哪些類不被混淆


-keepclassmembers class **.R$* {
    public static <fields>;
    public static final int *;
}
-keepclasseswithmembernames class * {  # 保持 native 方法不被混淆
    native <methods>;
}
-keepclasseswithmembers class * {   # 保持自定義控件類不被混淆
    public <init>(android.content.Context, android.util.AttributeSet);
}
-keepclasseswithmembers class * {# 保持自定義控件類不被混淆
    public <init>(android.content.Context, android.util.AttributeSet, int);
}
-keepclassmembers class * extends android.app.Activity { # 保持自定義控件類不被混淆
    public void *(android.view.View);
}
-keepclassmembers enum * {     # 保持枚舉 enum 類不被混淆
    public static **[] values();
    public static ** valueOf(java.lang.String);
}
-keep class * implements android.os.Parcelable { # 保持 Parcelable 不被混淆
    public static final android.os.Parcelable$Creator *;
}

-keep public class * extends android.view.View {
    public <init>(android.content.Context);
    public <init>(android.content.Context, android.util.AttributeSet);
     public <init>(android.content.Context, android.util.AttributeSet, int);
    public void set*(...);
    }
-keepclassmembers public class * extends android.view.View {
   void set*(***);
   *** get*();
}

#apk 包内所有 class 的内部結構
#-dump class_files.txt
#未混淆的類和成員
#-printseeds seeds.txt
#列出從 apk 中删除的代碼
#-printusage unused.txt
#混淆前後的映射
#-printmapping mapping.txt

#fastjson 可以混淆也可以不混淆
#-keep class javax.ws.rs.** { *; }
#-dontwarn com.alibaba.fastjson.**
#-keep class com.alibaba.fastjson.** { *; }
-keepclassmembers class * implements java.io.Serializable {
    static final long serialVersionUID;
    private static final java.io.ObjectStreamField[] serialPersistentFields;
    private void writeObject(java.io.ObjectOutputStream);
    private void readObject(java.io.ObjectInputStream);
    java.lang.Object writeReplace();
    java.lang.Object readResolve();
    public <fields>;
}
-keepattributes Signature

#gson
-dontwarn com.google.gson.**
-keep class com.google.gson.** { *;}
-keepclassmembers class * implements java.io.Serializable {
    static final long serialVersionUID;
    private static final java.io.ObjectStreamField[] serialPersistentFields;
    private void writeObject(java.io.ObjectOutputStream);
    private void readObject(java.io.ObjectInputStream);
    java.lang.Object writeReplace();
    java.lang.Object readResolve();
    public <fields>;
}

#v4
-dontwarn android.support.v4.**
-keep class android.support.v4.** { *; }
-keep interface android.support.v4.app.** { *; }
-keep public class * extends android.support.v4.**
-keep public class * extends android.app.Fragment

#greendao
#-dontwarn de.greenrobot.dao.**
#-keep class de.greenrobot.dao.** { *;}
-keepclassmembers class * extends de.greenrobot.dao.AbstractDao {
    public static java.lang.String TABLENAME;
}
-keep class **$Properties

# 對于帶有回調函數的onXXEvent、**On*Listener的,不能被混淆
-keepclassmembers class * {
    void *(**On*Event);
    void *(**On*Listener);
}

# webView處理,項目中沒有使用到webView忽略即可
-keepclassmembers class fqcn.of.javascript.interface.for.webview {
    public *;
}
-keepclassmembers class * extends android.webkit.webViewClient {
    public void *(android.webkit.WebView, java.lang.String, android.graphics.Bitmap);
    public boolean *(android.webkit.WebView, java.lang.String);
}
-keepclassmembers class * extends android.webkit.webViewClient {
    public void *(android.webkit.webView, jav.lang.String);
}

#

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

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

           

踩了一個坑,過來補充一下。

問題:繼承了微信分享後沒有引入依賴造成分享無法使用。

是以在要混淆檔案中加入

-keep class com.tencent.mm.opensdk.** {*;}
-keep class com.tencent.wxop.** {*;}
-keep class com.tencent.mm.sdk.** {*;}