Android 微信Tinker 熱修復(fù) 踩坑記錄

簡(jiǎn)述

熱更新能力是Bugly為解決開(kāi)發(fā)者緊急修復(fù)線上bug,無(wú)需重新發(fā)版讓用戶(hù)無(wú)感知就能把問(wèn)題修復(fù)的一項(xiàng)能力。 通過(guò)管理后臺(tái)發(fā)布補(bǔ)丁,進(jìn)行的修復(fù)。
這里先給出Bugly Android熱更新使用指南 文檔,能力強(qiáng)的小伙伴們可以自己去過(guò)一遍。

老規(guī)矩先上圖(感覺(jué)圖片沒(méi)有很好體現(xiàn)熱修復(fù)的效果)

未修復(fù)前,點(diǎn)擊提示按鈕提示 1+1=3 的錯(cuò)誤提示


未修復(fù)前.png

發(fā)布補(bǔ)丁后 ,點(diǎn)擊提示: 修復(fù) 1+1=2


熱修復(fù)啟動(dòng)后.png

采用的方案原理

微信Tinker熱修復(fù)采用的是類(lèi)加載方案,重啟App后讓ClassLoader重新加載新的類(lèi) ,故無(wú)法實(shí)時(shí)生效,得需要用戶(hù)殺掉進(jìn)程關(guān)掉APP后,重新啟動(dòng)進(jìn)來(lái)才有效果。 由于已經(jīng)有大佬對(duì)相關(guān)幾種修復(fù)原理進(jìn)行分析,咱們可以站在巨人肩膀上學(xué)習(xí)學(xué)習(xí):這里直接貼劉望舒大佬傳送門(mén)地址

開(kāi)啟代碼之旅

開(kāi)始之前先說(shuō)說(shuō)我的開(kāi)發(fā)版本:

  • gradle 版本 我測(cè)試過(guò)的3.3.0 和3.4.0 可以正常接入使用,但是接入3.6.1 就會(huì)出現(xiàn)提示
Gradle sync failed: No signature of method: org.gradle.api.internal.file.DefaultFilePropertyFactory$DefaultDirectoryVar.getFiles() is applicable for argument types: () values: []

此類(lèi)錯(cuò)誤本人暫無(wú)能力解決,有解決方案的小伙伴可以提出來(lái),大家互相學(xué)習(xí)學(xué)習(xí)。
我這邊是開(kāi)啟混淆,并且設(shè)置了混淆規(guī)則 接入方式用enableProxyApplication = false 的情況,這個(gè)是關(guān)閉映射官方推薦的方式(為true的成本低,文檔也容易,就不重復(fù)寫(xiě))
Bugly平臺(tái)小伙伴們自己注冊(cè)和創(chuàng)建,然后取創(chuàng)建項(xiàng)目的appid,這幾個(gè)步驟相信不用貼出來(lái),小伙伴們也能搗鼓清楚。

先按照開(kāi)發(fā)文檔,把我們需要的依賴(lài)的SDK配置進(jìn)來(lái)

  • 首先是項(xiàng)目下的gradle


    根目錄下的gradle配置.png

    在dependencies 中設(shè)置插件依賴(lài)

 // tinkersupport插件, 其中l(wèi)astest.release指拉取最新版本,也可以指定明確版本號(hào),例如1.0.4
  classpath "com.tencent.bugly:tinker-support:1.1.5"
  • 在app 目錄下的gradle 中集成SDK
    app module的build.gradle.png
    implementation "com.android.support:multidex:1.0.1" // 多dex配置
    //注釋掉原有bugly的倉(cāng)庫(kù)
    //compile 'com.tencent.bugly:crashreport:latest.release'//其中l(wèi)atest.release指代最新版本號(hào),也可以指定明確的版本號(hào),例如1.3.4
    implementation 'com.tencent.bugly:crashreport_upgrade:1.3.6'
    // 指定tinker依賴(lài)版本(注:應(yīng)用升級(jí)1.3.5版本起,不再內(nèi)置tinker)
    implementation 'com.tencent.tinker:tinker-android-lib:1.9.9'
    implementation 'com.tencent.bugly:nativecrashreport:latest.release'
    //其中l(wèi)atest.release指代最新版本號(hào),也可以指定明確的版本號(hào),例如2.2.0
  • 同步完成之前順便把生成簽名也設(shè)置了混淆(這邊為了方便調(diào)試,把release 與 debug 同時(shí)配置為使用密鑰,同時(shí)開(kāi)啟混淆 ,單純只是為了寫(xiě)個(gè)測(cè)試的可以minifyEnabled 設(shè)置為false)
    fix.jks密鑰目錄位置:


    fix.jks.png

完整gradler如下:

apply plugin: 'com.android.application'
// 依賴(lài)插件腳本
apply from: 'tinker-support.gradle'
android {
    compileSdkVersion 29
    buildToolsVersion "29.0.3"
    signingConfigs {
        config {
            keyAlias 'guo'
            keyPassword 'g123456'
            storeFile file("fix.jks")
            storePassword 'g123456'
        }
    }
    defaultConfig {
        applicationId "com.mutou.fixapplication"
        minSdkVersion 16
        targetSdkVersion 29
        versionCode 1
        versionName "1.0.1"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
        ndk {
            //設(shè)置支持的SO庫(kù)架構(gòu)
            abiFilters 'armeabi', 'x86'//, 'x86', 'armeabi-v7a', 'x86_64', 'arm64-v8a'
        }
    }

    buildTypes {
        release {
            minifyEnabled true
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
            signingConfig signingConfigs.config
        }
        debug {
            minifyEnabled true

            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
            //signingConfig signingConfigs.config
            signingConfig signingConfigs.config
        }

    }

}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])

    implementation 'androidx.appcompat:appcompat:1.2.0'
    implementation 'androidx.constraintlayout:constraintlayout:2.0.1'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'androidx.test.ext:junit:1.1.2'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'

    implementation "com.android.support:multidex:1.0.1" // 多dex配置
    //注釋掉原有bugly的倉(cāng)庫(kù)
    //compile 'com.tencent.bugly:crashreport:latest.release'//其中l(wèi)atest.release指代最新版本號(hào),也可以指定明確的版本號(hào),例如1.3.4
    implementation 'com.tencent.bugly:crashreport_upgrade:1.3.6'
    // 指定tinker依賴(lài)版本(注:應(yīng)用升級(jí)1.3.5版本起,不再內(nèi)置tinker)
    implementation 'com.tencent.tinker:tinker-android-lib:1.9.9'
    implementation 'com.tencent.bugly:nativecrashreport:latest.release'
    //其中l(wèi)atest.release指代最新版本號(hào),也可以指定明確的版本號(hào),例如2.2.0
}

  • 建議把SDK集成完成后再配置插件腳本
 apply from: 'tinker-support.gradle'

在app目錄下新建文件 tinker-support.gradle


目錄截圖.png

我這邊是已經(jīng)存在了故報(bào)了這個(gè)錯(cuò)誤,你們點(diǎn)擊 ok 即可生成該文件


新建文件截圖.png

該插件配置直接照著文檔copy即可,相關(guān)參數(shù)含義可以參見(jiàn)文檔。
配置如下

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

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

/**
 * 此處填寫(xiě)每次構(gòu)建生成的基準(zhǔn)包目錄
 */
def baseApkDir = "app-0916-16-41-06"

/**
 * 對(duì)于插件各參數(shù)的詳細(xì)解析請(qǐng)參考
 */
tinkerSupport {

    // 開(kāi)啟tinker-support插件,默認(rèn)值true
    enable = true

    // 指定歸檔目錄,默認(rèn)值當(dāng)前module的子目錄tinker
    autoBackupApkDir = "${bakPath}"

    // 是否啟用覆蓋tinkerPatch配置功能,默認(rèn)值false
    // 開(kāi)啟后tinkerPatch配置不生效,即無(wú)需添加tinkerPatch
    overrideTinkerPatchConfiguration = true

    // 編譯補(bǔ)丁包時(shí),必需指定基線版本的apk,默認(rèn)值為空
    // 如果為空,則表示不是進(jìn)行補(bǔ)丁包的編譯
    // @{link tinkerPatch.oldApk }
    baseApk = "${bakPath}/${baseApkDir}/app-release.apk"

    // 對(duì)應(yīng)tinker插件applyMapping
    baseApkProguardMapping = "${bakPath}/${baseApkDir}/app-release-mapping.txt"

    // 對(duì)應(yīng)tinker插件applyResourceMapping
    baseApkResourceMapping = "${bakPath}/${baseApkDir}/app-release-R.txt"

    // 構(gòu)建基準(zhǔn)包和補(bǔ)丁包都要指定不同的tinkerId,并且必須保證唯一性 base-1.0.1 對(duì)應(yīng) patch-1.0.1
    tinkerId = "patch-1.0.1"

    // 構(gòu)建多渠道補(bǔ)丁時(shí)使用
    // buildAllFlavorsDir = "${bakPath}/${baseApkDir}"

    // 是否啟用加固模式,默認(rèn)為false.(tinker-spport 1.0.7起支持)
    // isProtectedApp = true

    // 是否開(kāi)啟反射Application模式
    enableProxyApplication = false

    // 是否支持新增非export的Activity(注意:設(shè)置為true才能修改AndroidManifest文件)
    supportHotplugComponent = true

}

/**
 * 一般來(lái)說(shuō),我們無(wú)需對(duì)下面的參數(shù)做任何的修改
 * 對(duì)于各參數(shù)的詳細(xì)介紹請(qǐng)參考:
 * 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" //  可選,設(shè)置mapping文件,建議保持舊apk的proguard混淆方式
        //applyResourceMapping = "${bakPath}/${appName}/app-release-R.txt" // 可選,設(shè)置R.txt文件,通過(guò)舊apk文件保持ResId的分配
    }
}
  • 再來(lái)配置我們的混淆規(guī)則 app目錄下的 proguard-rules.pro (gradle minifyEnabled 設(shè)置為false的可以跳過(guò) )
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
#   http://developer.android.com/guide/developing/tools/proguard.html

# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
#   public *;
#}

# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable

# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

-dontwarn com.tencent.bugly.**
-keep public class com.tencent.bugly.**{*;}
# tinker混淆規(guī)則
-dontwarn com.tencent.tinker.**
-keep class com.tencent.tinker.** { *; }
-keep class android.support.**{*;}

# 繼承了DefaultApplicationLike 的類(lèi) 記得保持不被混淆,否則找不到該類(lèi)直接閃退
-keep public class com.mutou.fixapplication.AppContext{*;}


-optimizationpasses 5

# 混合時(shí)不使用大小寫(xiě)混合,混合后的類(lèi)名為小寫(xiě)
-dontusemixedcaseclassnames
# 指定不去忽略非公共庫(kù)的類(lèi)
-dontskipnonpubliclibraryclasses
# 這句話能夠使我們的項(xiàng)目混淆后產(chǎn)生映射文件
# 包含有類(lèi)名->混淆后類(lèi)名的映射關(guān)系
-verbose
# 指定不去忽略非公共庫(kù)的類(lèi)成員
-dontskipnonpubliclibraryclassmembers
# 不做預(yù)校驗(yàn),preverify是proguard的四個(gè)步驟之一,Android不需要preverify,去掉這一步能夠加快混淆速度。
-dontpreverify
# 保留Annotation不混淆
-keepattributes *Annotation*,InnerClasses
# 避免混淆泛型
-keepattributes Signature
# 拋出異常時(shí)保留代碼行號(hào)
-keepattributes SourceFile,LineNumberTable
# 指定混淆是采用的算法,后面的參數(shù)是一個(gè)過(guò)濾器
# 這個(gè)過(guò)濾器是谷歌推薦的算法,一般不做更改
-optimizations !code/simplification/cast,!field/*,!class/merging/*
#############################################
#
# Android開(kāi)發(fā)中一些需要保留的公共部分
#
#############################################
# 保留我們使用的四大組件,自定義的Application等等這些類(lèi)不被混淆
# 因?yàn)檫@些子類(lèi)都有可能被外部調(diào)用
-keep public class * extends android.app.Activity
-keep public class * extends android.app.Appliction
-keep public class * extends android.app.Service
-keep public class * extends android.content.BroadcastReceiver
-keep public class * extends android.content.ContentProvider
-keep public class * extends android.app.backup.BackupAgentHelper
-keep public class * extends android.preference.Preference
-keep public class * extends android.view.View
-keep public class com.android.vending.licensing.ILicensingService

# 保留R下面的資源
-keep class **.R$* {*;}

# 保留本地native方法不被混淆
-keepclasseswithmembernames class * {
    native <methods>;
}

# 保留在Activity中的方法參數(shù)是view的方法,
-keepclassmembers class * extends android.app.Activity{
    public void *(android.view.View);
}

# 保留枚舉類(lèi)不被混淆
-keepclassmembers enum * {
    public static **[] values();
    public static ** valueOf(java.lang.String);
}

# support
-keep public class * extends android.support.v4.**
-keep public class * extends android.support.v7.**
-keep public class * extends android.support.annotation.**

# androidx的混淆
-keep class com.google.android.material.** {*;}
-keep class androidx.** {*;}
-keep public class * extends androidx.**
-keep interface androidx.** {*;}
-dontwarn com.google.android.material.**
-dontnote com.google.android.material.**
-dontwarn androidx.**

# 保留我們自定義控件(繼承自View)不被混淆
-keep public class * extends android.view.View{
    *** get*();
    void set*(***);
    public <init>(android.content.Context);
    public <init>(android.content.Context, android.util.AttributeSet);
    public <init>(android.content.Context, android.util.AttributeSet, int);
}

# 保留Parcelable序列化類(lèi)不被混淆
-keep class * implements android.os.Parcelable {
    public static final android.os.Parcelable$Creator *;
}

# 保留Serializable序列化的類(lèi)不被混淆
-keepclassmembers class * implements java.io.Serializable {
    static final long serialVersionUID;
    private static final java.io.ObjectStreamField[] serialPersistentFields;
    !static !transient <fields>;
    !private <fields>;
    !private <methods>;
    private void writeObject(java.io.ObjectOutputStream);
    private void readObject(java.io.ObjectInputStream);
    java.lang.Object writeReplace();
    java.lang.Object readResolve();
}

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

# webview 還要注意native接口
-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);
}

# keep annotated by NotProguard
-keep @top.andnux.proguard.annotation.Keep class * {*;}
-keep class * {
    @top.andnux.proguard.annotation.Keep <fields>;
}
-keepclassmembers class * {
    @top.andnux.proguard.annotation.Keep <methods>;
}

# 刪除代碼中Log相關(guān)的代碼
-assumenosideeffects class android.util.Log {
    public static boolean isLoggable(java.lang.String, int);
    public static int v(...);
    public static int i(...);
    public static int w(...);
    public static int d(...);
    public static int e(...);
}


其中-keep public class com.mutou.fixapplication.AppContext{*;}
這個(gè)看各位小伙伴自己建立的項(xiàng)目包名和類(lèi)名,這個(gè)繼承了DefaultApplicationLike 的類(lèi) 記得保持不被混淆,不然Application代理類(lèi)找不到路徑就閃退了。

  • 自定義我們的CustomTinkerApplication 來(lái)繼承TinkerApplication ,在構(gòu)造函數(shù)里直接調(diào)用父類(lèi)TinkerApplication 帶四個(gè)參數(shù)的構(gòu)造函數(shù)(具體代表含義參考文檔)
public class CustomTinkerApplication extends TinkerApplication {
    public CustomTinkerApplication() {
        super(ShareConstants.TINKER_ENABLE_ALL,
                "com.mutou.fixapplication.AppContext",
                "com.tencent.tinker.loader.TinkerLoader",
                false);
    }
}

其中第二個(gè)參數(shù) "com.mutou.fixapplication.AppContext" 是我們自定義的Application代理類(lèi),注意路徑正確和寫(xiě)全,這是我包的目錄結(jié)構(gòu),截圖僅供參考,小伙伴們更改為自己項(xiàng)目的正確環(huán)境


目錄結(jié)構(gòu).png
  • 將該類(lèi)配置到清單manifest中


    AndroidManifest.png
  • 自定義AppContext并且實(shí)現(xiàn)SDK初始化

public class AppContext extends DefaultApplicationLike {
    public AppContext(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();
        // 這里實(shí)現(xiàn)SDK初始化,appId替換成你的在Bugly平臺(tái)申請(qǐng)的appId
        // 調(diào)試時(shí),將第三個(gè)參數(shù)改為true
        Bugly.init(getApplication(), "7eda4c07f3", 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);

        // 安裝tinker
        // TinkerManager.installTinker(this); 替換成下面Bugly提供的方法
        Beta.installTinker(this);
    }


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

其中 Bugly 第二個(gè)參數(shù)是我在平臺(tái)上申請(qǐng)的項(xiàng)目的AppId ,第三個(gè)參數(shù)設(shè)置為開(kāi)啟debug ,其他小伙伴們?cè)谡江h(huán)境可以關(guān)掉. 我們其他集成的SDK的初始化也在這里面實(shí)現(xiàn)。

  • AndroidManifest 進(jìn)行權(quán)限配置
<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" />

其中里面有些需要?jiǎng)討B(tài)申請(qǐng)權(quán)限,小伙伴們注意下,我這邊為求方便,直接用5.0的手機(jī)測(cè)試,若是高于6.0測(cè)試時(shí)候最好自行打開(kāi)所有權(quán)限。

  • Activity配置
    在application 標(biāo)簽內(nèi)的,注意位置(四大組件的注冊(cè))
        <activity
            android:name="com.tencent.bugly.beta.ui.BetaActivity"
            android:configChanges="keyboardHidden|orientation|screenSize|locale"
            android:theme="@android:style/Theme.Translucent" />
  • 配置FileProvider,這邊建議繼承FileProvider類(lèi)來(lái)解決合并沖突,一般第三方拍照裁剪的庫(kù),會(huì)解決7.0 上遇到的Uri 問(wèn)題,如果依賴(lài)了就會(huì)和這邊的設(shè)置起沖突,我們一口氣走一遍完整的解決沖突問(wèn)題(單純想測(cè)試的可直接按照文檔來(lái))
    新建類(lèi)UriProvider 并且繼承FileProvider
public class UriProvider extends FileProvider {
}

在res目錄新建xml文件夾,創(chuàng)建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>

截圖如下:


provider_paths.png

在清單上配置

        <provider
            android:name=".providers.UriProvider"
            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>

name 是我們創(chuàng)建該類(lèi)的路徑與自身類(lèi)名,小伙伴們自行改更

到這里寫(xiě)完了我們SDK的接入了,接下來(lái)這邊簡(jiǎn)單使用下補(bǔ)丁發(fā)布,先來(lái)生成我們的基準(zhǔn)包,里面寫(xiě)個(gè)錯(cuò)誤的提示,代碼和布局過(guò)于簡(jiǎn)單,只貼下活動(dòng)頁(yè)代碼了


MainActivity.png
  • 生成基準(zhǔn)包
    在tinker-support中,基準(zhǔn)包的 tinkerId 我這邊設(shè)置為 base-1.0.1 (后面不要命名一模一樣的其他基準(zhǔn)包,以免沖突)


    image.png

然后在AS右上角 的Gradle 中找到并且執(zhí)行assembleRelease編譯生成基準(zhǔn)包


Gradle .png
assembleRelease.png

若是沒(méi)有找到的,可以在other目錄下找找,我的便是在里面找到的


other目錄.png

雙擊運(yùn)行之后,會(huì)生成編譯的基準(zhǔn)包、混淆配置文件(開(kāi)啟混淆才有)、資源Id,這份文件建議保存起來(lái)。


bakApk.png

這個(gè)就是我們要用于生產(chǎn)環(huán)境的包(現(xiàn)在里面的有錯(cuò)誤提示的代碼),然后找臺(tái)手機(jī)安裝打開(kāi),之后會(huì)聯(lián)網(wǎng)上報(bào)(確保網(wǎng)絡(luò)正常),在我們的Bugly平臺(tái)中能看到統(tǒng)計(jì)

  • 生成補(bǔ)丁包
    還是在我們的tinker-support中,這次要修改的有兩處:
  1. baseApkDir ,將其設(shè)置為我們生成的基準(zhǔn)包目錄的文件名(生成的是時(shí)間戳),我這邊的是app-0916-16-41-06


    baseApkDir .png
  1. 將tinkerId 設(shè)置為 patch-1.0.1


    tinkerId .png

然后在AS右上角那地方找到tinker-suppor插件目錄下的buildTinkerPatchRelease 雙擊運(yùn)行


tinker-suppor插件目錄.png

生成的補(bǔ)丁包在app\build\outputs\apk\tinkerPatch\release目錄下(我這邊AS顯示不出,但是按照路徑上便能找到),小伙伴們可以直接找到的。


image.png
  • 將patch_signed_7zip補(bǔ)丁包上傳
    打開(kāi)我們的管理平臺(tái),選中我們創(chuàng)建的項(xiàng)目


    發(fā)布補(bǔ)丁.png
補(bǔ)丁信息補(bǔ)充.png

管理平臺(tái)可以看到 1/全量設(shè)備, 說(shuō)明已經(jīng)激活一臺(tái),熱修復(fù)成功了。
整個(gè)流程就到此為止。希望能對(duì)小伙伴們有啟發(fā),不足之處還望指出。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容