對於廣大移動開發者而言,App的版本更新升級是再尋常不過的事。但是當你發現你剛發出去的包有緊急Bug需要修復時,你就不淡定了,又要經過繁瑣的傳統的App版本更新流程,重新釋出一個修復Bug的版本,再將Apk上傳到各大應用商店,使用者需要花費時間去應用商店重新下載安裝。如果Bug比較嚴重,有些使用者可能會失去耐心,直接解除安裝掉App,於是乎,你們的使用者就這樣流失了。
傳統的更新流程有幾個弊端,一是重新發布版本費時費力代價高,二是使用者安裝需要去應用商店下載成本高,三是不能及時的修復bug,使用者體驗差。但是H5的出現使使這種情況有了小小的轉機,把需要經常變更的業務邏輯以H5的方式獨立出來,再嵌入App中。為什麼說是小小的轉機,因為App中仍有原生的程式碼,你不能保證原生的程式碼不會出Bug。於是乎,熱更新就應運而生。
目前國內的熱更新技術有很多,比如阿里的AndFix、Sophix;微信的Tinker;QQ空間的超級補丁;餓了嗎的Amigo;美團的Robust等,各有優缺點。實現程式碼修復主要有兩大方案:阿里系的底層替換和騰訊系的類載入。底層替換限制比較多,但能立即載入補丁包實現熱修復。類載入需要冷啟動才能使熱修復生效,但限制少,修復的範圍廣。有興趣的同學都可以去了解下。推薦一本阿里的工程師出的熱修復書籍《深入探索Android熱修復技術原理》,內容詳細的講解了熱修復原理。今天我主要教大家如何將騰訊的基於Tinker的熱修復框架Bugly整合到專案中,接下來開始講整合的步驟。
第一步:新增外掛依賴 工程根目錄下“build.gradle”檔案中新增:
buildscript {
repositories {
jcenter()
}
dependencies {
// tinkersupport外掛, 其中latest.release指拉取最新版本,也可以指定明確版本號,例如1.0.4
classpath "com.tencent.bugly:tinker-support: latest.release"
}
}
複製程式碼
第二步:整合SDK gradle配置 在app module的“build.gradle”檔案中新增(示例配置):
android {
defaultConfig {
}
}
dependencies {
compile "com.android.support:multidex:1.0.1" // 多dex配置
//註釋掉原有bugly的倉庫
//compile 'com.tencent.bugly:crashreport:latest.release'
//其中latest.release指代最新版本號,也可以指定明確的版本號,例如2.3.2
compile 'com.tencent.bugly:crashreport_upgrade:1.3.4'
}
複製程式碼
在app module的“build.gradle”檔案中新增:
// 依賴外掛指令碼
apply from: 'tinker-support.gradle'
複製程式碼
tinker-support.gradle內容如下所示(示例配置): 注:您需要在同級目錄下建立tinker-support.gradle這個檔案哦。
apply plugin: 'com.tencent.bugly.tinker-support'
def bakPath = file("${buildDir}/bakApk/")
/**
* 此處填寫每次構建生成的基準包目錄
*/
def baseApkDir = "app-0208-15-10-00"
/**
* 對於外掛各引數的詳細解析請參考
*/
tinkerSupport {
// 開啟tinker-support外掛,預設值true
enable = true
// 指定歸檔目錄,預設值當前module的子目錄tinker
autoBackupApkDir = "${bakPath}"
// 是否啟用覆蓋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 = "1.0.0-base"
// 構建多渠道補丁時使用
// buildAllFlavorsDir = "${bakPath}/${baseApkDir}"
// 是否啟用加固模式,預設為false.(tinker-spport 1.0.7起支援)
// isProtectedApp = true
// 是否開啟反射Application模式
enableProxyApplication = false
supportHotpugComponent = 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的分配
}
}
複製程式碼
第三步:初始化SDK enableProxyApplication = false 的情況 這是Tinker推薦的接入方式,一定程度上會增加接入成本,但具有更好的相容性。 整合Bugly升級SDK之後,我們需要按照以下方式自定義ApplicationLike來實現Application的程式碼(以下是示例): 自定義Application
public class MyApplication extends TinkerApplication {
public MyApplication() {
super(ShareConstants.TINKER_ENABLE_ALL, "xxx.xxx.MyApplicationLike",
"com.tencent.tinker.loader.TinkerLoader", false);
}
}
複製程式碼
別忘了將MyApplication 加入到AndroidMenifest中的application標籤下的name屬性中。
自定義ApplicationLike
public class MyApplicationLike extends DefaultApplicationLike {
public MyApplicationLike(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();
// 這裡實現SDK初始化,appId替換成你的在Bugly平臺申請的appId
// 除錯時,將第三個引數改為true
Bugly.init(getApplication(), "fb2bada5b4", 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);
}
}
複製程式碼
enableProxyApplication = true 時直接在你的Application的onCreate()方法中呼叫 Bugly.init(getApplication(), "fb2bada5b4", true);
第四步:AndroidManifest.xml配置 在AndroidMainfest.xml中進行以下配置: 許可權配置
<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" />
複製程式碼
第五步:混淆配置 為了避免混淆SDK,在Proguard混淆檔案中增加以下配置:
-dontwarn com.tencent.bugly.**
-keep public class com.tencent.bugly.**{*;}
複製程式碼
如果你使用了support-v4包,你還需要配置以下混淆規則:
-keep class android.support.**{*;}
複製程式碼
到此為止,已經成功的整合了Bugly框架
接下來測試熱修復
1、編譯基準包 配置基準包的tinkerId
執行assembleRelease編譯生成基準包:
這個會在build/bakApk路徑下生成每次編譯的基準包、混淆配置檔案、資源Id檔案,如下圖所示:
啟動上一步生成的app-release.apk,啟動後,SDK會自動上報聯網資料 我們每次冷啟動都會請求補丁策略,會上報當前版本號和tinkerId,這樣我們後臺就能將這個唯一的tinkerId對應到一個版本。
2、對基線版本的bug修復 未修復前
修復後
3、根據基線版本生成補丁包 修改待修復apk路徑、mapping檔案路徑、resId檔案路徑和tinkerId
執行構建補丁包的task
生成的補丁包在build/outputs/patch目錄下:
4、上傳補丁包到平臺 上傳補丁包到平臺並下發編輯規則
選擇檔案後會自動識別目標版本,若出現“未匹配到可用補丁的App版本”,如果你的基線版本沒有上報過聯網,基於這個版本生成的補丁包就無法匹配到,啟動之前生成的app-release.apk,啟動後,SDK會自動上報聯網資料
5、測試補丁應用效果 啟動基線版本App,點選顯示文字時崩潰,只是我們前面造的一個空指標異常,重新啟動基線版本App,等待一兩分鐘後,會開始自動下載補丁包,下載成功之後會立即合成補丁,我配置了Beta.canNotifyUserRestart = true ,所以會彈出對話方塊,提醒使用者更新應用。由於Tinker需要再次冷啟動才能使補丁生效,點選重啟應用後會退出,再重新啟動apk,點選顯示文字,文字內容顯示為修復後的內容,之前的的空指標異常就被成功修復了。
Bugly還支援全量升級、異常上報、執行統計,詳情可以看Bugly官方文件:https://bugly.qq.com/docs/ 本文demo: https://github.com/isJoker/WanBuglyHotFixDemo