Android Tinker熱修復
應該都遇到過剛上線的app,突然發現一個嚴重的bug需要修復,怎麼辦,以前的做法修改bug,然後重新打包app,測試,發新版本。即使是僅僅改動一行程式碼,也需要這麼繁瑣的操作來修復bug。
後來,出現熱修復技術,使用者在使用過程中不知不覺bug就修復了。
現在已經出現了很多熱修復技術了,這裡記錄Tinker的整合接入,更加詳細的內容可以查閱官方文件
Tinker 是一個開源專案(Github連結),它是微信官方的 Android 熱補丁解決方案,它支援動態下發程式碼、So 庫以及資源,讓應用能夠在不需要重新安裝的情況下實現更新。
Tinker SDK接入
第一步 新增 gradle 外掛依賴,在專案的builder.gradle裡面新增TinkerPatch 外掛
buildscript {
repositories {
jcenter()
}
dependencies {
// TinkerPatch 外掛
classpath "com.tinkerpatch.sdk:tinkerpatch-gradle-plugin:1.1.7"
}
}
第二步 整合 TinkerPatch SDK
在app的gradle檔案app/build.gradle中
dependencies {
// 若使用annotation需要單獨引用,對於tinker的其他庫都無需再引用
provided("com.tinkerpatch.tinker:tinker-android-anno:1.7.11")
compile("com.tinkerpatch.sdk:tinkerpatch-android-sdk:1.1.7")
}
第三步 配置 tinkerpatchSupport 引數
在app目錄下,建立tinkerpatch.gradle
為了簡單方便,我們將 TinkerPatch 相關的配置都放於 tinkerpatch.gradle 中, 我們需要在app的gradle檔案app/build.gradle中還新增:
apply from : 'tinkerpatch.gradle'
開啟引入的 tinkerpatch.gradle 檔案,它的具體引數如下:
apply plugin: 'tinkerpatch-support'
/**
* TODO: 請按自己的需求修改為適應自己工程的引數
*/
def bakPath = file("${buildDir}/bakApk/")
def baseInfo = "app-1.0.0-0803-17-03-11"// 注意!!! 改成對應的路徑
def variantName = "release"
/**
* 對於外掛各引數的詳細解析請參考
* http://tinkerpatch.com/Docs/SDK
*/
tinkerpatchSupport {
/** 可以在debug的時候關閉 tinkerPatch **/
tinkerEnable = true
reflectApplication = true
autoBackupApkPath = "${bakPath}"
appKey = "1fb8b4539cbc48db"// 注意!!! 需要修改成你的appkey
/** 注意: 若釋出新的全量包, appVersion一定要更新 **/
appVersion = "1.0.0"
def pathPrefix = "${bakPath}/${baseInfo}/${variantName}/"
def name = "${project.name}-${variantName}"
baseApkFile = "${pathPrefix}/${name}.apk"
baseProguardMappingFile = "${pathPrefix}/${name}-mapping.txt"
baseResourceRFile = "${pathPrefix}/${name}-R.txt"
/**
* 若有編譯多flavors需求, 可以參照: https://github.com/TinkerPatch/tinkerpatch-flavors-sample
* 注意: 除非你不同的flavor程式碼是不一樣的,不然建議採用zip comment或者檔案方式生成渠道資訊(相關工具:walle 或者 packer-ng)
**/
}
/**
* 用於使用者在程式碼中判斷tinkerPatch是否被使能
*/
android {
defaultConfig {
buildConfigField "boolean", "TINKER_ENABLE", "${tinkerpatchSupport.tinkerEnable}"
}
}
/**
* 一般來說,我們無需對下面的引數做任何的修改
* 對於各引數的詳細介紹請參考:
* https://github.com/Tencent/tinker/wiki/Tinker-%E6%8E%A5%E5%85%A5%E6%8C%87%E5%8D%97
*/
tinkerPatch {
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
}
}
將以上程式碼完整複製到tinkerpatch.gradle中
引數說明
bakPath:基包路徑
baseInfo:基包資料夾名(打補丁包的時候,需要修改)
appKey:進入官網註冊一個賬號,新增APP,得到對應的appKey。
第四步 初始化 TinkerPatch SDK
1. reflectApplication = true 的情況
若我們使用 reflectApplication 模式,我們無需為接入 Tinker 而改造我們的 Application 類。
public class MyApplication extends Application {
private ApplicationLike tinkerApplicationLike;
@Override
public void onCreate() {
super.onCreate();
if (BuildConfig.TINKER_ENABLE) {
// 我們可以從這裡獲得Tinker載入過程的資訊
tinkerApplicationLike = TinkerPatchApplicationLike.getTinkerPatchApplicationLike();
// 初始化TinkerPatch SDK, 更多配置可參照API章節中的,初始化SDK
TinkerPatch.init(tinkerApplicationLike)
.reflectPatchLibrary()
.setPatchRollbackOnScreenOff(true)
.setPatchRestartOnSrceenOff(true);
// 每隔3個小時去訪問後臺時候有更新,通過handler實現輪訓的效果
new FetchPatchHandler().fetchPatchWithInterval(3);
Log.i("TAG", "tinker init");
}
}
}
- reflectApplication = false 的情況
若我們已經完成了應用的 Application 改造,即將 Application 的邏輯移動到 ApplicationLike類中
public class MyApplicationLike extends DefaultApplicationLike {
...
@Override
public void onCreate() {
super.onCreate();
if (BuildConfig.TINKER_ENABLE) {
// 我們可以從這裡獲得Tinker載入過程的資訊
tinkerApplicationLike = TinkerPatchApplicationLike.getTinkerPatchApplicationLike();
// 初始化TinkerPatch SDK, 更多配置可參照API章節中的,初始化SDK
TinkerPatch.init(tinkerApplicationLike)
.reflectPatchLibrary()
.setPatchRollbackOnScreenOff(true)
.setPatchRestartOnSrceenOff(true);
// 每隔3個小時去訪問後臺時候有更新,通過handler實現輪訓的效果
new FetchPatchHandler().fetchPatchWithInterval(3);
Log.i("TAG", "tinker init");
}
}
}
建立FetchPatchHandler用於檢測是否更新
public class FetchPatchHandler extends Handler {
public static final long HOUR_INTERVAL = 3600 * 1000;
private long checkInterval;
/**
* 通過handler, 達到按照時間間隔輪訓的效果
* @param hour
*/
public void fetchPatchWithInterval(int hour) {
//設定TinkerPatch的時間間隔
TinkerPatch.with().setFetchPatchIntervalByHours(hour);
checkInterval = hour * HOUR_INTERVAL;
//立刻嘗試去訪問,檢查是否有更新
sendEmptyMessage(0);
}
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
//這裡使用false即可
TinkerPatch.with().fetchPatchUpdate(false);
//每隔一段時間都去訪問後臺, 增加10分鐘的buffer時間
sendEmptyMessageDelayed(0, checkInterval + 10 * 60 * 1000);
}
}
最後在記得在Manifest.xml中配置Application,還要給SD卡讀寫許可權
<manifest >
.....
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<application
...
android:name=".MyApplication">
</manifest>
第五步 使用步驟
打正式包,打包前先配置簽名
在app/build.gradle中,配置簽名
//需要配置自己的簽名
signingConfigs {
release {
keyAlias '******' //簽名別名
keyPassword '******' //簽名密碼
storeFile file('******')//簽名檔案.keystore或者.jks
storePassword '******'//簽名密碼
}
}
buildTypes {
release {
signingConfig signingConfigs.release
}
}
開啟Studio右側的Gradle,選擇assemableRelease打正式包
完成後可以在資料夾build中找到生成的檔案(這裡稱為基包)
開啟build -> bakApk -> app-1.0.0-0803-17-03-11(根據時間命名)
release資料夾中會出現我們剛打完的包。一個apk,對應一個txt檔案。
將apk安裝到手機上,這是一個剛建立的專案,裡面只有Hello World
然後,修改程式碼,彈出一個這就是補丁…
Toast.makeText(this, "這是個補丁...", Toast.LENGTH_SHORT).show();
開啟tinkerpatch.gradle修改baseInfo成對應的檔名
修改tinkerpatch.gradle中的tinkerpatchSupport -> appVersion
完成後開啟Gradle,如下選擇tinkerPatchRelease
補丁包將位於 build/outputs/tinkerPatch 中,這裡只需要用到patch_signed_7zip.apk
最後把生成的補丁包,在Tinker官網網站釋出
版本號對應tinkerpatch.gradle中的appVersion
選擇patch_signed_7zip.apk檔案,提交即可(更多下發選項,參考官方文件)
提交後,檢視補丁的下載數量以及成功應用數
這裡補丁的成功率並不是100%,有時候應用下載了補丁,但是卻沒有更新,需要多試幾次,或者過會再試。我剛開始試的時候有時立刻就更新補丁了,有時試過好多次都沒更新,以為那裡出錯了,但是過會再試,又更新了。
提示:
1、新增SD卡許可權,
2、下載補丁後,殺掉程序重新開啟,補丁才會生效
3、補丁非即時生效,需要等一會兒