騰訊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方式生成渠道包。

參考文章並感謝

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章