在《Tinker + Bugly + Jenkins 爬坑之路》一文中講了在接入 Tinker 之後,Jenkins 中的一些坑,由此,熱修復算告一段落,但是,在直接 Run 模式執行時,程式會報出如下錯誤:
Tinker does not support instant run mode, please trigger build by assembleDebug or disable instant run in `File->Settings...`.
好吧,使用 TInker 時不能開啟 Instant Run  ̄□ ̄||
GitHub 上也有一個同樣的 issue,引入Tinker之後如何在Debug模式下開啟Instant Run ,這裡我將我的方法講述一下,給大家一個參考。
1. 使用變數標記是否使用 Tinker
在 project
的 build.gradle
檔案的 ext
中定義變數 tinkerEnabled 用來標記是否使用 TInker,程式碼如下所示:
ext {
/**
* 是否啟用tinker參與編譯
* 開發時,根據需要修改值來開啟
* Jenkins 構建時,會替換該值
*/
tinkerEnabled = rootProject.properties["tinkerEnable"]
if (null == tinkerEnabled) {
tinkerEnabled = "false"
}
}
看過《Tinker + Bugly + Jenkins 爬坑之路》的同學應該知道,我司的專案是使用 Jenkins 打包的,所以我這裡先通過 rootProject.properties["tinkerEnable"]
從 Gradle 命令中取 tinkerEnabled 引數的值,然後在構建指令碼的打包命令列中加入該引數:
sh gradlew assembleRelease -PtinkerEnable=true --stacktrace
這樣,就確保了 Jenkins 構建時 tinkerEnable 的值為 true。在開發過程中,本地執行或者構建 apk 就可以通過修改 tinkerEnabled = "false"
來決定是否使用 Tinker 構建。
2. 通過標記值決定是否使用 TInker 構建
接下來在 module 的 build.gradle
檔案中,通過 tinkerEnabled 值來判斷是否引入 tinker-support.gradle
構建專案,程式碼如下:
// 依賴外掛指令碼-tinker
if (Boolean.parseBoolean(rootProject.ext.tinkerEnabled)) {
apply from: rootProject.file(`gradle/tinker-support.gradle`)
}
3. Java/Kotlin 程式碼中通過標記值決定是否初始化 Tinker
在 Java/Kotlin
程式碼中,是無法直接使用 gradle 檔案中的變數值的,那麼在 Java/Kotlin
程式碼中,怎麼通過上面定義的標記量來決定是否初始化 Tinker 呢?總不能在 Java/Kotlin
程式碼中也定義一個全域性變數來控制吧,那樣本地開發時一改就得改兩個地方,不但麻煩而且可能出錯,另外,Jenkins 打包時也無法確定 Java/Kotlin
會初始化 Tinker。
那,怎麼辦呢?我們來個“曲線救國”的方法。通過自定義 BuildConfig 屬性來解決,首先,在 module 的 build.gradle
檔案中,將 tinkerEnabled 的值傳遞到 BuildConfig 的自定義屬性中,程式碼如下:
android {
compileSdkVersion rootProject.ext.compileSdkVersion
buildToolsVersion rootProject.ext.buildToolsVersion
defaultConfig {
/** =============自定義 BuildConfig 屬性========================*/
buildConfigField "boolean", "BuildConfig", rootProject.ext.tinkerEnabled
/** =============自定義 BuildConfig 屬性========================*/
}
}
然後,在自定義的 application 類中新增根據 BuildConfig.BuildConfig
判斷是否初始化 Tinker 的程式碼:
package com.cy.sample
import android.app.Application
import android.content.Context
import android.widget.Toast
import com.tencent.bugly.Bugly
import com.tencent.bugly.beta.Beta
import com.tencent.bugly.beta.interfaces.BetaPatchListener
import com.tencent.bugly.beta.tinker.TinkerManager.getApplication
import java.util.*
/**
* 類描述。
*
* @author cspecialy
* @version v1.0.0
*/
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
if (BuildConfig.TINKER_ENABLE) {
initTinker()
}
}
/**
* 初始化 Tinker
*/
private fun initTinker() {
// 設定是否開啟熱更新能力,預設為true
Beta.enableHotfix = true
// 設定是否自動下載補丁,預設為true
Beta.canAutoDownloadPatch = true
// 設定是否自動合成補丁,預設為true
Beta.canAutoPatch = true
// 設定是否提示使用者重啟,預設為false
Beta.canNotifyUserRestart = true
// 補丁回撥介面
Beta.betaPatchListener = object : BetaPatchListener {
override fun onPatchReceived(patchFile: String) {
Toast.makeText(getApplication(), "補丁下載地址$patchFile", Toast.LENGTH_SHORT).show()
}
override fun onDownloadReceived(savedLength: Long, totalLength: Long) {
Toast.makeText(getApplication(),
String.format(Locale.getDefault(), "%s %d%%",
Beta.strNotificationDownloading,
(if (totalLength == 0L) 0 else savedLength * 100 / totalLength).toInt()),
Toast.LENGTH_SHORT).show()
}
override fun onDownloadSuccess(msg: String) {
Toast.makeText(getApplication(), "補丁下載成功", Toast.LENGTH_SHORT).show()
}
override fun onDownloadFailure(msg: String) {
Toast.makeText(getApplication(), "補丁下載失敗", Toast.LENGTH_SHORT).show()
}
override fun onApplySuccess(msg: String) {
Toast.makeText(getApplication(), "補丁應用成功", Toast.LENGTH_SHORT).show()
}
override fun onApplyFailure(msg: String) {
Toast.makeText(getApplication(), "補丁應用失敗", Toast.LENGTH_SHORT).show()
}
override fun onPatchRollback() {
}
}
// 設定開發裝置,預設為false,上傳補丁如果下發範圍指定為“開發裝置”,需要呼叫此介面來標識開發裝置
Bugly.setIsDevelopmentDevice(getApplication(), true)
// 多渠道需求塞入
// String channel = WalleChannelReader.getChannel(getApplication());
// Bugly.setAppChannel(getApplication(), channel);
// 這裡實現SDK初始化,appId替換成你的在Bugly平臺申請的appId
Bugly.init(getApplication(), "2a1dc56c3a", true)
}
override fun attachBaseContext(base: Context) {
super.attachBaseContext(base)
// you must install multiDex whatever tinker is installed!
MultiDex.install(base)
if (BuildConfig.TINKER_ENABLE) {
// 安裝tinker
Beta.installTinker()
}
}
}
以上程式碼相信大家也注意到了,是的,我這裡 TInker 是使用 enableProxyApplication = true
開啟反射代理的方式,大家如果使用 enableProxyApplication = false
方式的話,方向也一樣,我這裡就不贅述了,大家因地制宜哈~~~ O(∩_∩)O哈哈~
接下來,開發時只需要將 tinkerEnabled 變數的值設定為 false,就可以愉快的使用 Instant Run 了。