大概目錄:
* Tinker 編譯相關(guān)問題?
* Tinker 庫中有什么類是不能修改的?
* 什么類需要放在主 dex 中?
* 我應(yīng)該使用哪個作為補丁包下發(fā),如何做多次修復(fù)?
* 如何對 Library 文件作補???
* 如何對資源文件作補丁,為什么有時候會提示大量沒有改變的圖片發(fā)生變更?
* Tinker 中的 dex 配置 'raw' 與 'jar' 模式應(yīng)該如何選擇?
* 如何兼容多渠道包?
* tinker 是否兼容加固?
* Google Play 版本是否可以有 Tinker 相關(guān)代碼?
* tinker 與 instant run 的兼容問題?
* 每次編譯我應(yīng)該保留哪些文件,如何兼容 AndResGuard?
* tinkerId 應(yīng)該如何選擇?
* 如何使生成的補丁包更???
* 關(guān)于使用的 ClassLoader 問題?
* 什么時候調(diào)用 installTinker?
* Proguard 5.2.1 applymapping 出現(xiàn) Warning?
* TinkerPatch 補丁管理后臺與 Tinker 的關(guān)系?
* Tinker 的最佳實踐?
一、Tinker 編譯相關(guān)問題?
編譯過程相關(guān)的 issue 請先查看是否是以下情況:
無法打開 sample 工程: 請使用單獨的 IDE 窗口打開
tinker-sample-android工程;tinkerId is not set:是因為沒有正確地配置 IDE 的git路徑,若不是通過clone方式下載tinker,需要本地手動commit一次。這里你也可以使用其他字符作為tinkerId;對于編譯與補丁時發(fā)生的異常,請到 Tinker 自定義擴(kuò)展 中查看具體錯誤碼的原因。并通過
"Tinker."過濾Tinker相關(guān)的日志提交到issue中;若自定義
TinkerResultService,請務(wù)必將新的Service添加到Manifest中;權(quán)限問題;請務(wù)必已經(jīng)將讀取
sdk權(quán)限添加到AndroidManifest.xml中,并且已允許權(quán)限運行;若使用
DefaultLifeCycle注解生成 Application,需要將原來 Application 的實現(xiàn)移動到ApplicationLike中,并將原來的 Application 類刪掉;關(guān)于 Application 的改造這一塊大家比較疑惑,這塊請認(rèn)真閱讀自定義 Application 類,大部分的 app 應(yīng)該都能在半小時內(nèi)完成改造。
如果出現(xiàn)
Class ref in pre-verified class resolved to unexpected implementation異常,請確認(rèn)以下幾點:Application中傳入ApplicationLike的參數(shù)時是否采用字符串而不是Class.getName方式;新的 Application 是否已經(jīng)加入到dex loader pattern中;額外添加到dex loader pattern中類的引用類也需要加載到loader pattern中。
二、Tinker 庫中有什么類是不能修改的?
Tinker 庫中不能修改的類一共有 26 個,即 com.tencent.tinker.loader.* 類。加上你的 Appliction 類,只有 26 個類是無法通過 Tinker 來修改的。即使類似 Tinker.java 等管理類,也是可以通過 Tinker 本身來修改。
注意,在 1.7.6 版本之前,我們需要手動將不能修改的類添加到 tinkerPatch.dex.loader pattern 中。對于 1.7.6 以后的版本會自動生成。
但是若使用 newApk 或者命令行編譯,需要手動添加 Application 類與 loader 類
三、什么類需要放在主 dex 中?
Tinker 并不干涉你分包與多 dex 的加載邏輯,但是你需要確保以下幾點:
com.tencent.tinker.loader.*類,你的 Application 類需要在主 dex,并且已經(jīng)在dex.loader中配置;若你自定義了
TinkerLoader類,你需要將TinkerLoader的自定義類,以及它用的到類也放在主 dex,并且已經(jīng)在dex.loader中配置;ApplicationLike的繼承類也需要放在主 dex 中,但是它無須在dex.loader中配置,因為它是可以使用 Tinker 修改的類。最后,如果你需要在加載其他 dex 之前加載 Tinker 的管理類,你也可以將com.tencent.tinker.*都加入到主 dex。你的
ApplicationLike實現(xiàn)類的直接引用類以及在調(diào)用Multidex install之前加載的類也都需要放到主 dex 中。
注意:Tinker 會自動生成需要放在主 dex 的 keep 規(guī)則。在 1.7.6 版本之前,你需要手動將生成規(guī)則拷貝到自己的 multiDexKeepProguard 文件中。例如 Sample 中的 multiDexKeepProguard file("keep_in_main_dex.txt")。在 1.7.6 版本之后,這里會通過腳本自動處理,無須手動填寫。
另外,如果 minsdkverion >= 21,multiDexEnabled 會被忽略。我們可以在 build/intermediates/multi-dex 查找最終的 keep 規(guī)則以及結(jié)果。
四、我應(yīng)該使用哪個作為補丁包下發(fā),如何做多次修復(fù)?
patch_signed_7zip.apk 是已簽名并且經(jīng)過 7z 壓縮的補丁包,但是你最好重命名一下,不要讓它以 .apk 結(jié)尾,這是因為有些運營商會挾持以 .apk 結(jié)尾的資源。
另外一點,我們在發(fā)起補丁請求時,需要先將補丁包先拷貝到 dataDir 中。因為在 sdcard 中,補丁包是極其容易被清理軟件刪除。這里可以參考 UpgradePatchRetry.java 的實現(xiàn)。
對于補丁包的版本問題,我們可以在 packageConfig 中增加,例如 sample 中的
packageConfig {
/**
* patch version via packageConfig
*/
configField("patchVersion", "1.0")
}
Tinker 支持對同一基準(zhǔn)版本做多次補丁修復(fù),在生成補丁時,oldApk 依然是已經(jīng)發(fā)布出去的那個版本。即補丁版本二的 oldApk 不能是補丁版本一,它應(yīng)該依然是用戶手機上已經(jīng)安裝的基準(zhǔn)版本。
五、如何對 Library 文件作補???
當(dāng)前官方并沒有直接將補丁的 lib 路徑添加到 DexPathList 中,理論上這樣可以做到程序完全沒有感知的對 Library 文件作補丁。這里主要是因為在多 abi 的情況下,某些機器獲取的并不準(zhǔn)確。當(dāng)前對 Library 文件作補丁可參考 Tinker API 概覽,tinker 1.7.7 版本中也提供了一鍵反射的方案給大家選擇。
大家可以根據(jù)自己的項目需要選擇合適的方案,事實上,無論是對 Library 還是 Application,官方都是采用盡量少去反射的策略,這也是為了提高 Tinker 框架的兼容性。上線前,我們應(yīng)當(dāng)嚴(yán)格測試補丁是否正確加載了修改后的 So 庫。
六、如何對資源文件作補丁,為什么有時候會提示大量沒有改變的圖片發(fā)生變更?
Tinker 采用全量合成方式實現(xiàn)資源替換,這里有以下幾點是使用者需要明確的:
remoteView是無法修改,例如transition動畫,notification icon以及桌面圖標(biāo);對于資源文件的更新(尤其是
assets),需要注意代碼中是否采用直接讀取sourceApk路徑方式讀取,這樣方式是無法更新的;
Tinker 只會將滿足res pattern的資源放在最后的合成補丁資源包中。一般為了減少合成資源大小,官方不建議輸入classes.dex或lib文件的pattern;若一個文件
:assets/classes.dex,它既滿足dex pattern,又滿足res pattern。Tinker 只會處理dex pattern,然后在合成資源包會忽略assets/classes.dex的變更。library 也是如此。只要資源發(fā)生變成的前提下 Tinker 才會合成新的資源包,這一定程度會增加占 Rom 體積,請在考慮后使用。
注意:若出現(xiàn)資源變更,我們需要使用 applyResourceMapping 方式編譯,這樣不僅可以減少補丁包大小,同時防止 remote view id 變更造成的異常情況。最后我們應(yīng)該查看編譯過程中生成的 resources_out.zip 是否滿足我們的要求。
有時候會發(fā)現(xiàn)大量明明沒有改變的 png 發(fā)現(xiàn)變更,解壓發(fā)現(xiàn)的確兩次編譯這些 png 的 md5 不一致。經(jīng)分析,aapt 在其中一次編譯將 png 優(yōu)化成 8-bit,另外一次卻沒有,從而導(dǎo)致 png 改變了。如果你們 app 出現(xiàn)了這種情況,官方建議關(guān)閉 aapt 對 png 的優(yōu)化:
aaptOptions{
cruncherEnabled false
}
若你對安裝包大小非常 care,可以提前使用命令行工具將所有圖片手動優(yōu)化一次。我們也可以選擇一些有損壓縮工具,獲得更大的壓縮效果。
如果你確認(rèn) png 并沒有修改,你可以在 tinker 的配置使用 ignoreChange 來忽略所有 png 文件的修改。
res {
ignoreChange = ["*.png"]
}
七、Tinker 中的 dex 配置 'raw' 與 'jar' 模式應(yīng)該如何選擇?
它們應(yīng)該說各有優(yōu)劣勢,大概應(yīng)該有以下幾條原則:
如果你的
minSdkVersion小于 14,那你務(wù)必要選擇 'jar' 模式;以一個
10M的 dex 為例,它壓縮成 jar 大約為4M,即 'jar' 模式能節(jié)省6M的 ROM 空間。對于 'jar' 模式,我們需要驗證壓縮包流中 dex 的 md5,這會更耗時,在小米 2S 上數(shù)據(jù)大約為 'raw' 模式
126 ms,'jar' 模式為246 ms。
因為在合成過程中 Tinker 已經(jīng)校驗了各個文件的 Md5,并將它們存放在 /data/data/.. 目錄中。默認(rèn)每次加載時 Tinker 并不會去校驗 tinker 文件的 Md5,但是你也可通過開啟 loadVerifyFlag 強制每次加載時校驗,但是這會帶來一定的時間損耗。
簡單來說,'jar' 模式更省空間,但是運行時校驗的耗時大約為 'raw' 模式的兩倍。如果你沒有打開運行時校驗,推薦使用 'jar' 模式。
八、如何兼容多渠道包?
關(guān)于渠道包的問題,若使用 flavor 編譯渠道包,會導(dǎo)致不同的渠道包由于 BuildConfig 變化導(dǎo)致 classes.dex 差異。這里建議的方式有:
將渠道信息寫在
AndroidManifest.xml或文件中,例如channel.ini;將渠道信息寫在
apk文件的zip comment中,這種是建議方式,例如可以使用項目 packer-ng-plugin 或者可使用V2 Scheme的 walle;若不同渠道存在功能上的差異,建議將差異部分放于單獨的 dex 或采用相同代碼不同配置方式實現(xiàn);
事實上,tinker 也支持多 flavor 直接編譯多個補丁包,具體可參考多 Flavor 打包。
九、tinker 是否兼容加固?
tinker 1.7.8 可以通過 isProtectedApp 開啟加固支持,這種模式僅僅可以使用在加固應(yīng)用中。
| 加固廠商 | 測試 |
|---|---|
| 騰訊云·樂固 | Tested |
| 愛加密 | Tested |
| 梆梆加固 | Tested |
| 360加固 | Tested,需要 5 月 8 日以后加固的版本 |
| 其他 | 請自行測試,只要滿足下面規(guī)則的都可以支持 |
這里是否支持加固,需要加固廠商明確以下兩點:
不能提前導(dǎo)入類;
在
art平臺若要編譯oat文件,需要將內(nèi)聯(lián)取消。
十、Google Play 版本是否可以有 Tinker 相關(guān)代碼?
由于 Google play 的使用者協(xié)議,對于 GP 渠道我們不能使用 Tinker 動態(tài)更新代碼,這里會存在應(yīng)用被下架的風(fēng)險。但是在 Google play 版本,我們依然可以存在 Tinker 的相關(guān)代碼,但是我們需要屏蔽補丁的網(wǎng)絡(luò)請求與合成相關(guān)操作。
十一、tinker 與 instant run 的兼容問題?
事實上,若編譯時都使用 assemble*,tinker 與 instant run 是可以兼容的。但是不少用戶基礎(chǔ)包與補丁包混用兩種模式導(dǎo)致補丁過大,所以 tinker 編譯時禁用 instant run,我們可以在設(shè)置中禁用 instant run 或使用 assemble 方式編譯。
大家日常 debug 時若想開啟 instant run 功能,可以將 tinker 暫時關(guān)閉:
ext {
//for some reason, you may want to ignore tinkerBuild, such as instant run debug build?
tinkerEnabled = false
}
十二、每次編譯我應(yīng)該保留哪些文件,如何兼容 AndResGuard?
正如 sample 中 app/build.gradle,每個可能用到 Tinker 發(fā)布補丁的版本,需要在編譯后保存以下幾個文件:
編譯后生成的
apk文件,即用來編譯補丁的基礎(chǔ)版本;若使用
proguard混淆,需要保持mapping.txt文件;需要保留編譯時的
R.txt文件;若你同時使用了資源混淆組件 AndResGuard,你也需要將混淆資源的
resource_mapping.txt保留下來,同時將r/*也添加到res pattern中。具體我們可以參考 build.gradle。
微信通過將補丁編譯與 Jenkins 很好的結(jié)合起來,只需要點擊一個按鈕,即可方便的生成補丁包。也可以參考 tinkerpatch-andresguard-sample。
十三、tinkerId 應(yīng)該如何選擇?
tinkerId 是用來區(qū)分基準(zhǔn)安裝包的,我們需要嚴(yán)格保證一個基準(zhǔn)包的唯一性。在設(shè)計的初期,官方使用的是基準(zhǔn)包的 CentralDirectory 的 CRC,但某些 APP 為了生成渠道包會對安裝包重新打包,導(dǎo)致不同的渠道包的 CentralDirectory 并不一致。
編譯補丁包時,Tinker 會自動讀取基準(zhǔn)包 AndroidManifest 的 tinkerId 作為 package_meta.txt 中的 TINKER_ID。將本次編譯傳入的 tinkerId,作為 package_meta.txt 中的 NEW_TINKER_ID。當(dāng)前 NEW_TINKER_ID 并沒有被使用到,只是保留作為配置項。如果我們使用 git rev 作為 tinkerid,這時只要使用 git diff TINKER_ID NEW_TINKER_ID 即可獲得所有的代碼差異。
我們需要保證 tinkerId 一定是要唯一性的,這里推薦使用 git rev 或者 svn rev。如果我們升級了客戶端版本,但 tinkerId 與舊版本相同,會導(dǎo)致可能會加載舊版本的補丁。這里我們一定要注意,升級可客戶端版本,需要更新 tinkerId!
十四、如何使生成的補丁包更小?
對于代碼來說,我們最好記住以下幾條規(guī)則:
編譯補丁包時,
proguard使用applymapping模式;對于多 dex 的情況,保持原本的分包規(guī)則,盡量減少由于分包變化而帶來的變更。在生成補丁包過程中,對于 class 分包的變化將會輸出
Warning:Class Moved日志,我們應(yīng)該盡量減少這種變化;大量靜態(tài)常量的改變與資源 R 文件的變更,這里推薦使用
applyResouceMapping方式保持資源 ID。大量類分包的改變對補丁包的影響不大,但是對于合成的時間消耗與占 ROM 的體積影響更大。我們每次生成補丁后,都應(yīng)該查看TinkerPatch輸出文件夾的日志;其他的例如使用
force jumbo模式以及使用7zip壓縮補丁包。
十五、關(guān)于使用的 ClassLoader 問題?
Tinker 沒有使用 parent classloader 方案,而是使用 Multidex 插入 dexPathList 方式,這里主要考慮到分平臺內(nèi)部類可能存在校驗 classloader 的問題。
若
SDK >= 24,即 Android N 版本,當(dāng)補丁存在時,Tinker 將PathClassloader替換為AndroidNClassLoader,但是它依然繼承與PathClassLoader。我們依然可以像以往那樣對它進(jìn)行類似makeDexElements的操作。;若
SDK < 14,Tinker 沒有對classloader做處理,這里需要注意補丁的Dex是插入在dexElement的前方。
十六、什么時候調(diào)用 installTinker?
首先我們推薦在最開始的時候就是執(zhí)行 installTinker 操作,但是即使你不去 installTinker,也不會影響 Tinker 對代碼、So 與資源的加載。installTinker 只是做了以下幾件事件:
回調(diào)
LoadReporter,返回加載結(jié)果;初始化各個自定義類與 Tinker 實例,可以調(diào)用 Tinker 相關(guān) API,發(fā)起升級補丁以及處理相關(guān)的回調(diào)。
事實上,微信只在主進(jìn)程與 :patch 進(jìn)程執(zhí)行 installTinker 操作。其他進(jìn)程只要不處理回調(diào)結(jié)果,不發(fā)起補丁請求即可。在 SampleUncaughtExceptionHandler 中,為了防止 Crash 并沒有執(zhí)行 installTinker,全部使用的是 TinkerApplicationHelper 中的 API,詳細(xì)可以查看 Tinker API 概覽。
十七、Proguard 5.2.1 applymapping 出現(xiàn) Warning?
這是因為 5.2.1 增加了內(nèi)聯(lián)函數(shù)的行輸出信息導(dǎo)致,你可以使用以下幾種方法解決:
使用 5.1 版本
proguard;將內(nèi)聯(lián)函數(shù)的優(yōu)化關(guān)掉;
自己對
mapping文件去除內(nèi)聯(lián)函數(shù)的行信息。
如果使用 4.X 版本的 Proguard 強烈建議升級到 5.1 版本??梢韵认螺d 5.1 的 Proguard, 然后通過以下方式指定:
classpath files('proguard-5.1.jar')
若使用 gradle 編譯,與 multiDexKeepProguard 不同,我們無需將生成的 tinker_proguard.pro 拷貝到自己的配置中。另外一個方面,若 applymapping 過程出現(xiàn)沖突,我們可以采取以下幾個方法:
添加
ignoreWarning;需要注意的是如果某些類的確需要采用新的mapping,這樣補丁后 App 會出問題,一般并不建議采用這種方式;修改基準(zhǔn)包的
mapping文件;我們需要根據(jù)新的mapping文件,修正基準(zhǔn)包的mapping文件。例如將warning項刪掉或者將新mapping中keep的項復(fù)寫到基準(zhǔn)的mapping中??梢詤⒖寄_本 proguard_warning.py 與 merge_mapping.py。
注意,如果想通過直接刪除舊 mapping 文件的沖突項,需要注意刪除類的內(nèi)部類是否存在混淆沖突。
十八、TinkerPatch 補丁管理后臺與 Tinker 的關(guān)系?
TinkerPatch 平臺是第三方開發(fā)基于 CDN 分發(fā)的補丁管理后臺。它提供了補丁后臺托管,版本管理,一鍵傻瓜式接入等功能,讓我們可以無需修改任何代碼即可輕松接入Tinker。
我們可以根據(jù)自己的需要選擇接入,它是獨立于 Tinker 項目之外。
十九、Tinker 的最佳實踐?
為了使補丁的成功率更高,官方在 Sample 中還做了以下工作:
由于合成進(jìn)程可能被各種原因殺死,使用 UpgradePatchRetry.java 來做重試功能,提高成功率;
防止補丁后程序無法啟動,使用 SampleUncaughtExceptionHandler.java 做
crash啟動保護(hù)。這里更推薦的是進(jìn)入安全模式,使用配置的方式強制清理或者升級補丁;為了防止
BuildConfig的改變導(dǎo)致大量類的變更,使用 BuildInfo.java 非 final 的變量來中轉(zhuǎn)。為了加快補丁應(yīng)用同時保持用戶體驗,SampleResultService.java 在應(yīng)用退入后臺或手機滅屏?xí)r,才殺掉進(jìn)程。你也可以在殺掉進(jìn)程前,直接通過發(fā)送 broadcast 或 service intent 的方式盡快的重啟進(jìn)程。
把
jumboMode打開,防止由于字符串增多導(dǎo)致force-jumbol,導(dǎo)致更多的變更。使用
zip comment方式生成渠道包。