簡(jiǎn)述
熱更新能力是Bugly為解決開(kāi)發(fā)者緊急修復(fù)線(xiàn)上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ā)布補(bǔ)丁后 ,點(diǎn)擊提示: 修復(fù) 1+1=2
采用的方案原理
微信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 中集成SDKapp 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
我這邊是已經(jīng)存在了故報(bào)了這個(gè)錯(cuò)誤缸榄,你們點(diǎn)擊 ok 即可生成該文件
該插件配置直接照著文檔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í)鹰贵,必需指定基線(xiàn)版本的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
# 這句話(huà)能夠使我們的項(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)境
-
將該類(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
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è)代碼了
-
生成基準(zhǔn)包
在tinker-support中,基準(zhǔn)包的 tinkerId 我這邊設(shè)置為 base-1.0.1 (后面不要命名一模一樣的其他基準(zhǔn)包互广,以免沖突)
image.png
然后在AS右上角 的Gradle 中找到并且執(zhí)行assembleRelease編譯生成基準(zhǔn)包
若是沒(méi)有找到的敛腌,可以在other目錄下找找卧土,我的便是在里面找到的
雙擊運(yùn)行之后,會(huì)生成編譯的基準(zhǔn)包像樊、混淆配置文件(開(kāi)啟混淆才有)尤莺、資源Id,這份文件建議保存起來(lái)生棍。
這個(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中友酱,這次要修改的有兩處:
-
baseApkDir ,將其設(shè)置為我們生成的基準(zhǔn)包目錄的文件名(生成的是時(shí)間戳)柔纵,我這邊的是app-0916-16-41-06
baseApkDir .png
-
將tinkerId 設(shè)置為 patch-1.0.1
tinkerId .png
然后在AS右上角那地方找到tinker-suppor插件目錄下的buildTinkerPatchRelease 雙擊運(yùn)行
生成的補(bǔ)丁包在app\build\outputs\apk\tinkerPatch\release目錄下(我這邊AS顯示不出缔杉,但是按照路徑上便能找到),小伙伴們可以直接找到的搁料。
-
將patch_signed_7zip補(bǔ)丁包上傳
打開(kāi)我們的管理平臺(tái)或详,選中我們創(chuàng)建的項(xiàng)目
發(fā)布補(bǔ)丁.png
管理平臺(tái)可以看到 1/全量設(shè)備, 說(shuō)明已經(jīng)激活一臺(tái)加缘,熱修復(fù)成功了鸭叙。
整個(gè)流程就到此為止。希望能對(duì)小伙伴們有啟發(fā)拣宏,不足之處還望指出沈贝。