天天看點

熱修複架構-Tinker接入常見問題

參考資料:https://github.com/Tencent/tinker/wiki/Tinker-常見問題

Tinker編譯相關問題?

編譯過程相關的issue請先檢視是否是以下情況:

  1. 無法打開sample工程

    : 請使用單獨的IDE視窗打開tinker-sample-android工程;
  2. tinkerId is not set

    : 這是因為沒有正确的配置IDE的git路徑, 若不是通過clone方式下載下傳tinker,需要本地手動commit一次。這裡你也可以使用其他字元作為tinkerId;
  3. 對于編譯與更新檔時發生的異常,請到Tinker 自定義擴充中檢視具體錯誤碼的原因。并通過“Tinker.”過濾Tinker相關的日志送出到issue中;
  4. 若自定義TinkerResultService,請務必将新的Service添加到Manifest中;
  5. 權限問題;請務必已經将讀取sdk權限添加到AndroidManifest.xml中,并且已允許權限運作;
  6. 若使用

    DefaultLifeCycle

    注解生成Application,需要将原來Application的實作移動到ApplicationLike中,并将原來的Application類删掉;
  7. 關于Application的改造這一塊大家比較疑惑,這塊請認真閱讀自定義Application類,大部分的app應該都能在半小時内完成改造。
  8. 如果出現

    Class ref in pre-verified class resolved to unexpected implementation

    異常, 請确認以下幾點:Application中傳入ApplicationLike的參數時是否采用字元串而不是Class.getName方式;新的Application是否已經加入到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以後的版本會自動生成。

什麼類需要放在主dex中?

Tinker并不幹涉你分包與多dex的加載邏輯,但是你需要確定以下幾點:

  1. com.tencent.tinker.loader.*類,你的Application類需要在主dex,并且已經在dex.loader中配置;
  2. 若你自定義了TinkerLoader類,你需要将TinkerLoader的自定義類,以及它用的到類也放在主dex,并且已經在dex.loader中配置;
  3. ApplicationLike的繼承類也需要放在主dex中,但是它無須在dex.loader中配置,因為它是可以使用Tinker修改的類。最後

    如果你需要在加載其他dex之前加載Tinker的管理類,你也可以将com.tencent.tinker.*都加入到主dex

  4. 你的ApplicationLike實作類的直接引用類以及在調用Multidex install之前加載的類也都需要放到主dex中。

注意:Tinker會自動生成需要放在主dex的keep規則。在1.7.6版本之前,你需要手動将生成規則拷貝到自己的multiDexKeepProguard檔案中。例如Sample中的

multiDexKeepProguard file("keep_in_main_dex.txt")

。在1.7.6版本之後,這裡會通過腳本自動處理,無須手動填寫。

另外,如果minsdkverion >=21, multiDexEnabled會被忽略。我們可以在build/intermediates/multi-dex查找最終的keep規則以及結果。

我應該使用哪個作為更新檔包下發,如何做多次修複?

patch_signed_7zip.apk

是已簽名并且經過7z壓縮的更新檔包,但是你最好重命名一下,不要讓它以

.apk

結尾,這是因為有些營運商會挾持以

.apk

結尾的資源。

另外一點,我們在發起更新檔請求時,需要先将更新檔包先拷貝到dataDir中。因為在sdcard中,更新檔包是極其容易被清理軟體删除。這裡可以參考UpgradePatchRetry.java的實作。

對于更新檔包的版本問題,我們可以在packageConfig中增加,例如sample中的

packageConfig {
	/**
     * patch version via packageConfig
     */
     configField("patchVersion", "1.0")
}      

Tinker支援對同一基準版本做多次更新檔修複,在生成更新檔時,oldApk依然是已經釋出出去的那個版本。即更新檔版本二的oldApk不能是更新檔版本一,它應該依然是使用者手機上已經安裝的基準版本。

如何對Library檔案作更新檔?

目前我們并沒有直接将更新檔的lib路徑添加到

DexPathList

中,理論上這樣可以做到程式完全沒有感覺的對Library檔案作更新檔。這裡主要是因為在多abi的情況下,某些機器擷取的并不準确。目前對Library檔案作更新檔可參考Tinker API概覽,tinker 1.7.7版本我們也提供了一鍵反射的方案給大家選擇。

大家可以根據自己的項目需要選擇合适的方案,事實上,無論是對Library還是Application,我們都是采用盡量少去反射的政策,這也是為了提高Tinker架構的相容性。上線前,我們應當嚴格測試更新檔是否正确加載了修改後的So庫。

如何對資源檔案作更新檔,為什麼有時候會提示大量沒有改變的圖檔發生變更?

Tinker采用全量合成方式實作資源替換,這裡有以下幾點是使用者需要明确的:

  1. remoteView是無法修改,例如transition動畫,notification icon以及桌面圖示;
  2. 對于資源檔案的更新(尤其是assets),需要注意代碼中是否采用直接讀取sourceApk路徑方式讀取,這樣方式是無法更新的;
  3. Tinker隻會将滿足res pattern的資源放在最後的合成更新檔資源包中。一般為了減少合成資源大小,我們不建議輸入classes.dex或lib檔案的pattern;
  4. 若一個檔案:assets/classes.dex, 它既滿足dex pattern, 又滿足res pattern。Tinker隻會處理dex pattern, 然後在合成資源包會忽略assets/classes.dex的變更。library也是如此。
  5. 隻要資源發生變成的前提下我們才會合成新的資源包,這一定程度會增加占Rom體積,請在考慮後使用。

**Waringing:若出現資源變更,我們需要使用applyResourceMapping方式編譯,這樣不僅可以減少更新檔包大小,同時防止remote view id變更造成的異常情況。**最後我們應該檢視編譯過程中生成的

resources_out.zip

是否滿足我們的要求。

有時候會發現大量明明沒有改變的png發現變更,解壓發現的确兩次編譯這些png的md5不一緻。經分析,aapt在其中一次編譯将png優化成8-bit,另外一次卻沒有,進而導緻png改變了。如果你們app出現了這種情況,我們建議關閉aapt對png的優化:

aaptOptions{
	cruncherEnabled false
}      

若你對安裝包大小非常care,可以提前使用指令行工具将所有圖檔手動優化一次。我們也可以選擇一些有損壓縮工具,獲得更大的壓縮效果。

如果你确認png并沒有修改,你可以在tinker的配置使用ignoreChange來忽略所有png檔案的修改。

res {
	ignoreChange = ["*.png"]
}      

Tinker中的dex配置'raw'與'jar'模式應該如何選擇?

它們應該說各有優劣勢,大概應該有以下幾條原則:

  1. 如果你的minSdkVersion小于14, 那你務必要選擇'jar'模式;
  2. 以一個

    10M

    的dex為例,它壓縮成jar大約為

    4M

    ,即'jar'模式能節省

    6M

    的ROM空間。
  3. 對于'jar'模式,我們需要驗證壓縮包流中dex的md5,這會更耗時,在

    小米2S

    上資料大約為'raw'模式

    126ms

    , 'jar'模式為

    246ms

因為在合成過程中我們已經校驗了各個檔案的Md5,并将它們存放在/data/data/..目錄中。

預設每次加載時我們并不會去校驗tinker檔案的Md5

,但是你也可通過開啟loadVerifyFlag強制每次加載時校驗,但是這會帶來一定的時間損耗。

簡單來說,'jar'模式更省空間,但是運作時校驗的耗時大約為'raw'模式的兩倍。如果你沒有打開運作時校驗,推薦使用'jar'模式。

如何相容多管道包?

關于管道包的問題,若使用flavor編譯管道包,會導緻不同的管道包由于BuildConfig變化導緻classes.dex差異。這裡建議的方式有:

  1. 将管道資訊寫在AndroidManifest.xml或檔案中,例如channel.ini;
  2. 将管道資訊寫在apk檔案的zip comment中,這種是建議方式,例如可以使用項目packer-ng-plugin或者可使用V2 Scheme的walle;
  3. 若不同管道存在功能上的差異,建議将差異部分放于單獨的dex或采用相同代碼不同配置方式實作;

事實上,tinker也支援多flavor直接編譯多個更新檔包,具體可參考多Flavor打包。

tinker是否相容加強?

由于各個廠商的加強實作并不一緻,在1.7.6以及之後的版本,tinker不再支援加強的動态更新。

Google Play版本是否可以有Tinker相關代碼?

由于Google play的使用者協定,對于GP管道我們不能使用Tinker動态更新代碼,這裡會存在應用被下架的風險。但是在Google play版本,我們依然可以存在Tinker的相關代碼,但是我們需要屏蔽更新檔的網絡請求與合成相關操作。

tinker與instant run的相容問題?

事實上,若編譯時都使用assemble*, tinker與instant run是可以相容的。但是不少使用者基礎包與更新檔包混用兩種模式導緻更新檔過大,是以tinker編譯時禁用instant run,我們可以在設定中禁用instant run或使用assemble方式編譯。

大家日常debug時若想開啟instant run功能,可以将tinker暫時關閉:

ext {
    //for some reason, you may want to ignore tinkerBuild, such as instant run debug build?
    tinkerEnabled = false
}      

每次編譯我應該保留哪些檔案,如何相容AndResGuard?

正如sample中app/build.gradle,每個可能用到Tinker釋出更新檔的版本,需要在編譯後儲存以下幾個檔案:

  1. 編譯後生成的apk檔案,即用來編譯更新檔的基礎版本;
  2. 若使用proguard混淆,需要保持mapping.txt檔案;
  3. 需要保留編譯時的R.txt檔案;
  4. 若你同時使用了資源混淆元件AndResGuard, 你也需要将混淆資源的resource_mapping.txt保留下來,同時将

    r/*

    也添加到res pattern中。具體我們可以參考build.gradle。

微信通過将更新檔編譯與Jenkins很好的結合起來,隻需要點選一個按鈕,即可友善的生成更新檔包。

tinkerId應該如何選擇?

tinkerId是用了區分基準安裝包的,我們需要嚴格保證一個基準包的唯一性。在設計的初期,我們使用的是基準包的CentralDirectory的CRC,但某些APP為了生成管道包會對安裝包重新打包,導緻不同的管道包的CentralDirectory并不一緻。

編譯更新檔包時,我們會自動讀取基準包AndroidManifest的tinkerId作為package_meta.txt中的TINKER_ID。将本次編譯傳入的tinkerId, 作為package_meta.txt中的NEW_TINKER_ID。目前NEW_TINKER_ID并沒有被使用到,隻是保留作為配置項。如果我們使用git rev作為tinkerid, 這時隻要使用

git diff TINKER_ID NEW_TINKER_ID

即可獲得所有的代碼差異。

我們需要保證tinkerId一定是要唯一性的,這裡推薦使用git rev或者svn rev. 如果我們更新了用戶端版本,但tinkerId與舊版本相同,會導緻可能會加載舊版本的更新檔。這裡我們一定要注意,更新可用戶端版本,需要更新tinkerId!

如何使生成的更新檔包更小?

對于代碼來說,我們最好記住以下幾條規則:

  1. 編譯更新檔包時,proguard使用applymapping模式;
  2. 對于多dex的情況,

    保持原本的分包規則

    ,盡量減少由于分包變化而帶來的變更。在生成更新檔包過程中,對于class分包的變化将會輸出

    Warning:Class Moved

    日志, 我們應該盡量減少這種變化;
  3. 大量靜态常量的改變與資源R檔案的變更,這裡我們推薦使用applyResouceMapping方式保持資源ID。大量類分包的改變對更新檔包的影響不大,但是對于

    合成的時間消耗

    占ROM的體積

    影響更大。我們每次生成更新檔後,都應該檢視

    TinkerPatch

    輸出檔案夾的日志;
  4. 其他的例如使用force jumbo模式以及使用7zip壓縮更新檔包。

關于使用的ClassLoader問題?

Tinker沒有使用parent classloader方案,而是使用Multidex插入dexPathList方式,這裡主要考慮到分平台内部類可能存在校驗classloader的問題。

  1. 若SDK>=24, 即Android N版本,當更新檔存在時,我們将PathClassloader替換為AndroidNClassLoader, 但是它依然繼承與PathClassLoader。我們依然可以像以往那樣對它進行類似makeDexElements的操作。;
  2. 若SDK<14, 我們沒有對classloader做處理,這裡需要注意更新檔的Dex是插入在dexElement的前方。

什麼時候調用installTinker?

首先我們推薦在最開始的時候就是執行installTinker操作,但是即使你不去installTinker,也不會影響Tinker對代碼、So與資源的加載。installTinker隻是做了以下幾件事件:

  1. 回調LoadReporter,傳回加載結果;
  2. 初始化各個自定義類與Tinker執行個體,可以調用Tinker相關API,發起更新更新檔以及處理相關的回調。

事實上,微信隻在主程序與:patch程序執行installTinker操作。其他程序隻要不處理回調結果,不發起更新檔請求即可。在SampleUncaughtExceptionHandler中,為了防止Crash時并沒有執行

installTinker

,全部使用的是TinkerApplicationHelper中的API,詳細可以檢視Tinker API概覽。

Proguard 5.2.1 applymapping出現Warning?

這是因為5.2.1增加了内聯函數的行輸出資訊導緻,你可以使用以下幾種方法解決:

  1. 使用5.1版本proguard;
  2. 将内聯函數的優化關掉;
  3. 自己對mapping檔案去除内聯函數的行資訊。

如果使用 4.X 版本的 Proguard 強烈建議更新到 5.1 版本。可以先下載下傳 5.1的 Proguard, 然後通過以下方式指定:

classpath files('proguard-5.1.jar')
           

若使用gradle編譯,與multiDexKeepProguard不同,我們無需将生成的tinker_proguard.pro拷貝到自己的配置中。另外一個方面,若applymapping過程出現沖突,我們可以采取以下幾個方法:

  1. 添加ignoreWarning;需要注意的是如果某些類的确需要采用新的mapping,這樣更新檔後App會出問題,一般我們并不建議采用這種方式;
  2. 修改基準包的mapping檔案;我們需要根據新的mapping檔案,修正基準包的mapping檔案。例如将warning項删掉或者将新mapping中keep的項複寫到基準的mapping中。可以參考腳本proguard_warning.py與merge_mapping.py。

TinkerPatch更新檔管理背景與Tinker的關系?

TinkerPatch平台 是第三方開發基于CDN分發的更新檔管理背景。它提供了更新檔背景托管,版本管理,一鍵傻瓜式接入等功能,讓我們可以無需修改任何代碼即可輕松接入Tinker。

我們可以根據自己的需要選擇接入,它是獨立于Tinker項目之外。對于

Tencent/tinker

, 我們依然會以它的穩定性與性能作為第一要務。

Tinker的最佳實踐?

為了使更新檔的成功率更高,我們在Sample中還做了以下工作:

  1. 由于合成程序可能被各種原因殺死,使用UpgradePatchRetry.java來做重試功能,提高成功率;
  2. 防止更新檔後程式無法啟動,使用SampleUncaughtExceptionHandler.java做crash啟動保護。

    這裡更推薦的是進入安全模式

    ,使用配置的方式強制清理或者更新更新檔;
  3. 為了防止BuildConfig的改變導緻大量類的變更,使用BuildInfo.java非final的變量來中轉。
  4. 為了加快更新檔應用同時保持使用者體驗,在SampleResultService.java在應用退入背景或手機滅屏時,才殺掉程序。你也可以在殺掉程序前,直接通過發送broadcast或service intent的方式盡快的重新開機程序。
  5. 把jumboMode打開,防止由于字元串增多導緻force-jumbol,導緻更多的變更。
  6. 使用zip comment方式生成管道包。

更多的使用範例,大家請仔細閱讀Sample。Tinker架構支援高度自定義,若使用過程中有任何問題或建議,歡迎聯系我們!