1. 程式人生 > >騰訊Tinker 熱修復 Andriod studio 3.0 配置和整合(二)多渠道打包和補丁釋出

騰訊Tinker 熱修復 Andriod studio 3.0 配置和整合(二)多渠道打包和補丁釋出

騰訊Tinker 熱修復 Andriod studio 3.0 多渠道打包和釋出補丁方式推薦

本文說明
在之前我已經分享了Tinker 熱修復的 Andriod studio3.0 初次配置和整合,時隔這麼久來寫一下我對Thinker多渠道打包的理解和記錄,希望對大家有幫助。這篇文章寫的我覺得很淺對於新手完全ok,所以有大佬有更好的理解也可以留言和推薦,畢竟我能力有限哈。為啥這麼久沒寫因為我入職了很多時間去熟悉公司業務了,沒有時間。哈哈哈。下一篇將結合騰訊Buly完整實現熱修復 整合-補丁-下發-統計。

關於多渠道

為什麼多渠道

1 統計使用者安裝APP來源
知道多渠道的意義就不難理解多渠道打包主要是為了我們統計分析用的。所以,這樣就很清楚了我最初的想法不是不行,而是不好,對今後的產品分析沒有任何幫助。

2.批量修改生成的apk檔名
根據運營給的命名規則,如果是一個個的右鍵-重新命名,那15個還好,要真是有1000個,運營同事一定會拿刀找我的…而多渠道打包所有的都自動生成。

3.可更改包名
如果有生成不同包名的需求,通過gradle就可以解決。

4.生成不同應用名稱或圖示
有的時候你會在不同平臺看到XX-小米版,XX-魅族版等等,或者beta版的圖示和正式的不一樣,其實程式碼還是那個程式碼,無非做點小小的改動。

多渠道的方式

  • 按原理分
    大概有兩種,一個是通過gradle,另一個是美團介紹的只打一個包,然後解壓替換檔案。

  • 按打包方式
    美團的Walle
    360打包
    多Flavor打包
    python方式打包
    區別的話是打包速度和相容性這裡不做分析

本文使用的是美團的Walle
Walle(瓦力):Android Signature V2 Scheme簽名下的新一代渠道包打包神器

參考地址 https://tech.meituan.com/mt-apk-packaging.html
https://github.com/Meituan-Dianping/walle


Walle 使用和配置

Gradle外掛使用方式

1 配置build.gradle

在位於專案的根目錄 build.gradle 檔案中新增Walle Gradle外掛的依賴, 如下:

buildscript {
    dependencies {
        classpath 'com.meituan.android.walle:plugin:1.1.5'
    }
}

2 並在當前App的 build.gradle 檔案中apply這個外掛,並新增上用於讀取渠道號的AAR

apply plugin: 'walle'

dependencies {
    compile 'com.meituan.android.walle:library:1.1.5'
}

3 配置外掛

walle {
    // 指定渠道包的輸出路徑
    apkOutputFolder = new File("${project.buildDir}/outputs/channels");
    // 定製渠道包的APK的檔名稱
    apkFileNameFormat = '${appName}-${packageName}-${channel}-${buildType}-v${versionName}-${versionCode}-${buildTime}.apk';
    // 渠道配置檔案
    channelFile = new File("${project.getProjectDir()}/channel")
}

4 配置項具體解釋:

  • apkOutputFolder:指定渠道包的輸出路徑, 預設值為new File(“${project.buildDir}/outputs/apk”)

  • apkFileNameFormat:定製渠道包的APK的檔名稱, 預設值為’ appName {buildType}-${channel}.apk’
    可使用以下變數:

projectName - 專案名字
     appName - App模組名字
     packageName - applicationId (App包名packageName)
     buildType - buildType (release/debug等)
     channel - channel名稱 (對應渠道打包中的渠道名字)
     versionName - versionName (顯示用的版本號)
     versionCode - versionCode (內部版本號)
     buildTime - buildTime (編譯構建日期時間)
     fileSHA1 - fileSHA1 (最終APK檔案的SHA1雜湊值)
     flavorName - 編譯構建 productFlavors 名
  • channelFile:包含渠道配置資訊的檔案路徑。

channelFile

5 如何獲取渠道資訊
在需要渠道等資訊時可以通過下面程式碼進行獲取

String channel = WalleChannelReader.getChannel(this.getApplicationContext());

6 如何生成渠道包
生成渠道包的方式是和assemble${variantName}Channels指令結合,渠道包的生成目錄預設存放在 build/outputs/apk/,也可以通過walle閉包中的apkOutputFolder引數來指定輸出目錄

用法示例:

生成渠道包 ./gradlew clean assembleReleaseChannels

支援 productFlavors ./gradlew clean assembleMeituanReleaseChannels

7 更多用法請移步 https://github.com/Meituan-Dianping/walle

一個補丁修復所有渠道

我們為什麼使用Walle 來打包而不用productFlavors

  • 首先使用 productFlavors 用它來打渠道包是一個非常低效的做法,因為它每一次都會走編譯流程,你想一下如果每打一個渠道包就要走一下編譯流程,100個渠道包那得多慢。

  • 如果你要針對多渠道進行打補丁 你可能會回答,那就針對不同的渠道包進行打補丁。可是這是多麼低效。另外你配置渠道超過5個的話,那麼就意味著你要一個補丁,一個補丁上傳到Bugly補丁管理後臺,況且我們也只允許同時下發5個版本的補丁。這裡提一下為什麼要上傳所有渠道的補丁,因為通過productFlavors配置,會修改buildConfig類中的FLAVOR欄位,這會導致生成的不同渠道包的dex是不一樣的,所以只能針對具體渠道進行打補丁。這就非常的尷尬了

    我們使用Walle就沒有這些問題了

Buly

只需要上傳補丁包到補丁管理後臺,然後下發即可。

關於Thinker打包和補丁下發的一下常見問題

我應該使用哪個作為補丁包下發,如何做多次修復?

  • 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概覽,這裡以後需要考慮優化。

  • 另外一方面,對於第三方庫檔案的載入我們無法干預,但是隻要在我們的程式碼提前載入第三方的庫檔案即可。不過這裡確保我們使用的是同一個classloader來載入。

  • 無論是對Library還是Application,我們都是採用儘量少去反射的策略,這也是為了提高Tinker框架的相容性。上線前,我們應當嚴格測試補丁是否正確載入了修改後的So庫。不使用反射的另外一個好處是我們可以做的工作更多,例如載入前驗證它的MD5。

如何對資原始檔作補丁,為什麼有時候會提示大量沒有改變的圖片發生變更?

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

  • remoteView是無法修改,例如transition動畫,notification icon以及桌面圖示;
    對於資原始檔的更新(尤其是assets),需要注意程式碼中是否採用直接讀取sourceApk路徑方式讀取,這樣方式是無法更新的;

  • Tinker只會將滿足res pattern的資源放在最後的合成補丁資源包中。一般為了減少合成資源大小,我們不建議輸入classes.dex或lib檔案的pattern;

  • 若一個檔案:assets/classes.dex, 它既滿足dex pattern, 又滿足res pattern。Tinker只會處理dex pattern, 然後在合成資源包會忽略assets/classes.dex的變更。library也是如此。

  • 只要資源發生變成的前提下我們才會合成新的資源包,這一定程度會增加佔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’模式應該如何選擇?

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

  • 如果你的minSdkVersion小於14, 那你務必要選擇’jar’模式;
  • 以一個10M的dex為例,它壓縮成jar大約為4M,即’jar’模式能節省6M的ROM空間。
  • 對於’jar’模式,我們需要驗證壓縮包流中dex的md5,這會更耗時,在小米2S上資料大約為’raw’模式126ms, ‘jar’模式為246ms。

因為在合成過程中我們已經校驗了各個檔案的Md5,並將它們存放在/data/data/..目錄中。預設每次載入時我們並不會去校驗tinker檔案的Md5,但是你也可通過開啟loadVerifyFlag強制每次載入時校驗,但是這會帶來一定的時間損耗。

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

tinker是否相容加固?

需要整合升級SDK版本1.3.0以上版本才支援加固。

經過測試的加固產品:

騰訊樂固

愛加密

梆梆加固

360加固(SDK 1.3.1之後版本支援)

其他產品需要大家進行驗證。

tinker的一般模式需要Dex的合成,它並不支援加固,一定要使用加固的app可以使用usePreGeneratedPatchDex模式。由於加固會改變apk的dex結構,所以生成補丁包時我們務必要使用加固前的apk。

但是需要注意的是,某些加固工具會將非exported的四大元件的類名替換,對於這部分類即使使用usePreGeneratedPatchDex也無法修改。對於360加固,MainActivity由於被提前載入,也無法修復。大家對於加固的情況,請仔細測試,能否支援與加固的方式有關聯。

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

  • 由於Google play的使用者協議,對於GP渠道我們不能使用Tinker動態更新程式碼,這裡會存在應用被下架的風險。但是在Google play版本,我們依然可以存在Tinker的相關程式碼,但是我們需要遮蔽補丁的網路請求與合成相關操作。

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!

如何使生成的補丁包更小?

對於程式碼來說,我們最好記住以下幾條規則:

  • 編譯補丁包時,proguard使用applymapping模式;

  • 對於多dex的情況,保持原本的分包規則,儘量減少由於分包變化而帶來的變更。在生成補丁包過程中,對於class分包的變化將會輸出Warning:Class Moved日誌, 我們應該儘量減少這種變化;

  • 大量靜態常量的改變與資源R檔案的變更,這裡我們推薦使用applyResouceMapping方式保持資源ID。大量類分包的改變對補丁包的影響不大,但是對於合成的時間消耗與佔ROM的體積影響更大。我們每次生成補丁後,都應該檢視TinkerPatch輸出資料夾的日誌;

  • 其他的例如使用force jumbo模式以及使用7zip壓縮補丁包。

Tinker的最佳實踐?

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

  • 由於合成程序可能被各種原因殺死,使用UpgradePatchRetry.java來做重試功能,提高成功率;

  • 防止補丁後程序無法啟動,使用SampleUncaughtExceptionHandler.java做crash啟動保護。這裡更推薦的是進入安全模式,使用配置的方式強制清理或者升級補丁;

  • 為了防止BuildConfig的改變導致大量類的變更,使用BuildInfo.java非final的變數來中轉。

  • 為了加快補丁應用同時保持使用者體驗,在SampleResultService.java在應用退入後臺或手機滅屏時,才殺掉程序。你也可以在殺掉程序前,直接通過傳送broadcast或service intent的方式儘快的重啟程序。

  • 把jumboMode開啟,防止由於字串增多導致force-jumbol,導致更多的變更。

  • 使用zip comment方式生成渠道包。

參考文章並感謝