Android Tinker整合(含有AndResGuard資源混淆)
Tinker的原理
服務端做dex差量,將差量包下發到客戶端,在ART模式的機型上本地跟原apk中的classes.dex做merge,merge成為一個新的merge.dex後將merge.dex插入pathClassLoader的dexElement,為了實現差量包的最小化,Tinker自研了DexDiff/DexMerge演算法。Tinker還支援資源和So包的更新,So補丁包使用BsDiff來生成,資源補丁包直接使用檔案md5對比來生成,針對資源比較大的(預設大於100KB屬於大檔案)會使用BsDiff來對檔案生成差量補丁。

image
Tinker的整合
新增gradle依賴
在專案的build.gradle中,新增tinker-patch-gradle-plugin的依賴
classpath("com.tencent.tinker:tinker-patch-gradle-plugin:${TINKER_VERSION}") { changing = TINKER_VERSION?.endsWith("-SNAPSHOT") exclude group: 'com.android.tools.build', module: 'gradle' }
我是把TINKER_VERSION放到了gradle.properties裡
TINKER_VERSION=1.9.9
然後在app的gradle檔案app/ ofollow,noindex">build.gradle ,我們需要新增tinker的庫依賴以及apply tinker的gradle外掛.
apply plugin: 'com.tencent.tinker.patch' ... //tinker的核心庫 implementation("com.tencent.tinker:tinker-android-lib:${TINKER_VERSION}") { changing = true } annotationProcessor("com.tencent.tinker:tinker-android-anno:${TINKER_VERSION}") { changing = true } compileOnly("com.tencent.tinker:tinker-android-anno:${TINKER_VERSION}") { changing = true } //也需要這個依賴 implementation "com.android.support:multidex:1.0.3"
對於Application我們需要:
1.將我們自己Application類以及它的繼承類的所有程式碼拷貝到自己的ApplicationLike繼承類中,例如SampleApplicationLike。
2.Application的attachBaseContext方法實現要單獨移動到onBaseContextAttached中;
3.對ApplicationLike中,引用application的地方改成getApplication();
4.對其他引用Application或者它的靜態物件與方法的地方,改成引用ApplicationLike的靜態物件與方法;
完整專案請檢視 Tinkerdemo
打包
上線前,先執行installRelease任務,打出Apk,生成在備份目錄/bakApk/目錄中,同時,需要將該資料夾備份,作為下次熱更新的基準包;
打補丁
上線後,若出現bug,需要打補丁:
將之前備份好的檔案放置在app模組下的/build/bakApk/目錄下(檔案包含基準包,mapping.txt,R.txt)修改app模組下build.gradle中基準包目錄,如下:
//for normal build //old apk file to build patch apk tinkerOldApkPath = "${bakPath}/app-release-1024-17-38-43.apk" //proguard mapping file to build patch apk tinkerApplyMappingPath = "${bakPath}/app-release-1024-17-38-43-mapping.txt" //resource R.txt to build patch apk, must input if there is resource changed tinkerApplyResourcePath = "${bakPath}/app-release-1024-17-38-43-R.txt"
然後執行tinkerPatchRelease任務,生成補丁:
補丁包與相關日誌會儲存在/build/outputs/tinkerPatch/。然後我們將patch_signed_7zip.apk推送到手機的sdcard中。
adb push ./app/build/outputs/tinkerPatch/debug/patch_signed_7zip.apk/sdcard/
開啟app,點選LOAD PATCH按鈕, 如果看到patch success, please restart process的toast,表示載入成功,即可重新啟動app,就可以看到修改後的效果。
我們引入tinker之後,我們想debug直接執行時,就會報錯,這是我們需要把app的build.gradle的tinkerEnabled 修改為false
ext { //for some reason, you may want to ignore tinkerBuild, such as instant run debug build? tinkerEnabled = false .... }
相容AndResGuard
我們公司專案用到了資源混淆AndResGuard,tinker對資源混淆AndResGuard也是支援的,我們需要修改如下:
需要將混淆資源的resource_mapping.txt保留下來,等執行tinkerPatchRelease任務時我們需要用到resource_mapping.txt,
andResGuard { //tinker 需要保留此檔案 mappingFile = file("${buildDir}/bakApk/resguard/app-release-1029-17-36-41-resource_mapping.txt") //mappingFile = null ... }
同時將r/*也新增到res pattern中
res { /** * andresguard 混淆需要新增“r/*“ */ pattern = ["res/*","r/*","assets/*", "resources.arsc", "AndroidManifest.xml"] ... }
具體請看 build.gradle
執行resguardRelease任務,打出Apk,把生成的備份放到了/bakApk/resguard/目錄中
我們還需要修改build.gradle具體程式碼為
project.afterEvaluate { def date = new Date().format("MMdd-HH-mm-ss") def apkSuffix = "_7zip_aligned_signed.apk" /** * bak apk and mapping */ android.applicationVariants.all { variant -> /** * task type, you want to bak */ def taskName = variant.name String name = variant.name.toLowerCase() String destFilePrefix = "${project.name}-${name}" // find resguard task first def resguardTask = project.tasks.findByName("resguard${taskName.capitalize()}") if (resguardTask == null) { println("resguardTask not found, just return") return } def tinkerPatchTask = project.tasks.findByName("tinkerPatch${taskName.capitalize()}") if (tinkerPatchTask == null) { println("resguardTask not found, just return") return } tinkerPatchTask.doFirst { def buildApkPath = "${buildDir}/outputs/apk/${taskName}/AndResGuard_${project.getName()}-${taskName}/${project.getName()}-${taskName}${apkSuffix}" println("change tinkerPatchTask buildApkPath to resugurad output ${buildApkPath}") tinkerPatchTask.buildApkPath = buildApkPath } tinkerPatchTask.dependsOn resguardTask resguardTask.doLast { copy { from "${buildDir}/outputs/apk/${taskName}/AndResGuard_${project.getName()}-${taskName}/${project.getName()}-${taskName}_7zip_aligned_signed.apk" into file(bakPath.absolutePath + "/resguard") rename { String fileName -> fileName.replace("${project.getName()}-${taskName}_7zip_aligned_signed.apk", "${project.getName()}-${taskName}-${date}.apk") } from "${buildDir}/outputs/mapping/${taskName}/mapping.txt" into file(bakPath.absolutePath + "/resguard") rename { String fileName -> fileName.replace("mapping.txt", "${project.getName()}-${taskName}-${date}-mapping.txt") } from "${buildDir}/intermediates/symbols/${taskName}/R.txt" into file(bakPath.absolutePath + "/resguard") rename { String fileName -> fileName.replace("R.txt", "${project.getName()}-${taskName}-${date}-R.txt") } from "${buildDir}/outputs/apk/${taskName}/AndResGuard_${project.getName()}-${taskName}/resource_mapping_${project.getName()}-release.txt" into file(bakPath.absolutePath + "/resguard") rename { String fileName -> fileName.replace("resource_mapping_${project.getName()}-release.txt", "${project.getName()}-${taskName}-${date}-resource_mapping.txt") } } } } }
具體步驟就是
1. 上線前,先執行reguardRelease任務,把備份的了/bakApk/resguard/裡的檔案儲存下來。當做下次的基準包。
- 打補丁時,把將之前備份好的檔案放置在模組下的/build/bakApk/resguard/(檔案包含基準包,mapping.txt,R.txt,resource_mapping.txt),並修改build.guald
andResGuard { //tinker 需要保留此檔案 mappingFile = file("${buildDir}/bakApk/resguard/app-release-1029-17-36-41-resource_mapping.txt") //mappingFile = null ... } ... ext { //for some reason, you may want to ignore tinkerBuild, such as instant run debug build? tinkerEnabled = true //for normal build //old apk file to build patch apk tinkerOldApkPath = "${bakPath}/resguard/app-release-1029-17-36-41.apk" //proguard mapping file to build patch apk tinkerApplyMappingPath = "${bakPath}/resguard/app-release-1029-17-36-41-mapping.txt" //resource R.txt to build patch apk, must input if there is resource changed tinkerApplyResourcePath = "${bakPath}/resguard/app-release-1029-17-36-41-R.txt" }
執行tinkerPatchRelease任務, 補丁包會儲存在/build/outputs/tinkerPatch/裡,至此我們就拿到了補丁包,就可以用來下發給APP,來載入補丁。
完整專案請檢視 Tinkerdemo