本文導語:
本文的核心內容介紹:
(1)對比當前市場上的熱修復方案,對Tinker熱修復方案進行了簡單的介紹。
(2)詳細講解了微信Tinker的完整接入過程,文末提供了一個自己寫的非常輕量的Demo,可以幫助開發者迅速實現自己項目中熱修復的接入,將熱修復技術運用到真實的項目中,而不僅僅是Demo測試。
(3)加入了walle的多渠道打包方案,能迅速打出很多個渠道包。詳細的介紹了真實項目上線時APK及補丁包的版本維護,如何通過單個補丁包,修復多個渠道,進行熱修復的實現方案。
(4)文章末尾總結了接入過程中可能遇到的坑,及相應的解決方案,能幫助你無障礙的接入Tinker。以及簡單的分享了一些關于熱修復技術方面需要儲備的一些技術知識。
希望讀完本文的朋友,能對熱修復及相關的技術和概念,有比較深的理解。
一、Tinker熱修復方案原理簡介
1、騰訊官方介紹:
Tinker is a hot-fix solution library for Android, it supports dex, library and resources update without reinstalling apk.
Tinker 是一個開源項目(Github鏈接),它是微信官方的 Android 熱補丁解決方案,它支持動態下發代碼、So 庫以及資源,讓應用能夠在不需要重新安裝的情況下實現更新。
2、Tinker原理理解:
Tinker將old.apk(也就是下面要講到的基準包,上線發布時的APK)和new.apk,進行對比,得到patch.dex,然后應用程序通過在代碼中加入初始化tinker的代碼,可以實現在程序運行的時候加載patch.dex(補丁文件),然后patch.dex與本機APK的classex.dex合并,生成新的classes.dex。運行時通過反射將合并后的dex文件放置在加載的dexElements數組的前面。運行時替代的原理,其實和Qzone的方案差不多,都是去反射修改dexElements。
3、為什么要使用熱修復?
(1) 看看傳統的App升級更新流程:
如上圖,隨著移動端業務復雜程度的增加,傳統的APP更新流程顯然無法滿足業務和開發者的需求,無論是對于用戶還是開發維護人員,過程過于繁瑣,不夠靈活。
主要存在以下幾個弊端:
- 對于開發者而言,重新發布版本代價太大。
- 用戶下載安裝成本太高,可能失去耐心而直接卸載。
-
bug修復不及時,用戶體驗差。
(2) 再看看熱修復的開發流程,明顯更加靈活。
image.png
熱修復的幾大優勢:
- 無需重新發版,實時高效,開發的維護成本降低。
- 補丁靜默安裝,用戶無感知就能實現Bug的修復,體驗好。
- 修復成功率高,把損失降到最低。
4、為何選擇使用騰訊的Tinker修復方案?
當前市面的熱補丁方案有很多,其中比較出名的有阿里的 AndFix、美團的 Robust 以及 QZone 的超級補丁方案。但它們都存在無法解決的問題。其中AndFix可能接入是最簡單的一個(和Tinker命令行接入方式差不多),不過兼容性還是是有一定的問題的;QZone方案對性能會有一定的影響,且在Art模式下出現內存錯亂的問題;美團提出的思想方案主要是基于Instant Run的原理,兼容性比較好,但目前尚未開源。
使用Tinker的原因:
Tinker熱補丁方案不僅支持類、So 以及資源的替換,它還是2.X-7.X的全平臺支持。利用Tinker我們不僅可以用做 bugfix,甚至可以替代功能的發布。Tinker 已運行在微信的數億 Android 設備上,那么為什么你不使用 Tinker 呢?
(偷偷告訴你:其實現在最好的熱修復方案,是阿里2017年6月份發布的新一代非侵入式Android熱修復方案——Sophix,不過本人是在去年上半年就開始使用熱修復技術了,所以那會市面上的熱修復技術,相較而言,Tinker是最優的選擇,而且也經過了本人實際項目中的使用,所以我覺得大家在項目中如果還沒有使用過熱修復,那Tinker是很不錯的選擇,畢竟Tinker 已運行在微信的數億 Android 設備上。對于阿里的Sophix,有興趣的研究的朋友們,推薦大家可以去研讀《Android熱修復技術原理》)
二、Tinker接入步驟詳解
①工程的根目錄的build.gradle中配置:
// TinkerPatch 插件
classpath "com.tinkerpatch.sdk:tinkerpatch-gradle-plugin:1.2.6"
compile "com.android.support:multidex:1.0.1"
②app的build.gradle中添加TinkerPatch的SDK依賴:
compile "com.tinkerpatch.sdk:tinkerpatch-android-sdk:1.2.6"
③為了簡單方便,我們將 TinkerPatch 相關的配置都放于tinkerpatch.gradle中, 我們需要在app的build.gradle中將其引入:
apply from: 'tinkerpatch.gradle'
④tinkerpatch.gradle將其放在跟build.gradle同一級目錄即可,tinkerpatch.gradle中的完整配置如下。
apply plugin: 'tinkerpatch-support'
/**
* TODO: 請按自己的需求修改為適應自己工程的參數
*/
def bakPath = file("${buildDir}/bakApk/")
def baseInfo = "app-1.0.0-0527-01-08-12"
def variantName = "release"
/**
* 對于插件各參數的詳細解析請參考
* http://tinkerpatch.com/Docs/SDK
*/
tinkerpatchSupport {
/** 可以在debug的時候關閉 tinkerPatch **/
/** 當disable tinker的時候需要添加multiDexKeepProguard和proguardFiles,
這些配置文件本身由tinkerPatch的插件自動添加,當你disable后需要手動添加
你可以copy本示例中的proguardRules.pro和tinkerMultidexKeep.pro,
需要你手動修改'tinker.sample.android.app'本示例的包名為你自己的包名, com.xxx前綴的包名不用修改
**/
tinkerEnable = true
reflectApplication = true
/**
* 是否開啟加固模式,只能在APK將要進行加固時使用,否則會patch失敗。
* 如果只在某個渠道使用了加固,可使用多flavors配置
**/
protectedApp = false
/**
* 實驗功能
* 補丁是否支持新增 Activity (新增Activity的exported屬性必須為false)
**/
supportComponent = true
autoBackupApkPath = "${bakPath}"
appKey = "在tinkpatch管理后臺創建你的應用,會有一個唯一的appkey值,填入此處即可"
/** 注意: 若發布新的全量包, appVersion一定要更新 **/
appVersion = "1.0.0"
def pathPrefix = "${bakPath}/${baseInfo}/${variantName}/"
def name = "${project.name}-${variantName}"
baseApkFile = "${pathPrefix}/${name}.apk"
baseProguardMappingFile = "${pathPrefix}/${name}-mapping.txt"
baseResourceRFile = "${pathPrefix}/${name}-R.txt"
/**
* 若有編譯多flavors需求, 可以參照: https://github.com/TinkerPatch/tinkerpatch-flavors-sample
* 注意: 除非你不同的flavor代碼是不一樣的,不然建議采用zip comment或者文件方式生成渠道信息(相關工具:walle 或者 packer-ng)
**/
}
/**
* 用于用戶在代碼中判斷tinkerPatch是否被使能
*/
android {
defaultConfig {
buildConfigField "boolean", "TINKER_ENABLE", "${tinkerpatchSupport.tinkerEnable}"
}
}
/**
* 一般來說,我們無需對下面的參數做任何的修改
* 對于各參數的詳細介紹請參考:
* https://github.com/Tencent/tinker/wiki/Tinker-%E6%8E%A5%E5%85%A5%E6%8C%87%E5%8D%97
*/
tinkerPatch {
ignoreWarning = false
useSign = true
dex {
dexMode = "jar"
pattern = ["classes*.dex"]
loader = []
}
lib {
pattern = ["lib/*/*.so"]
}
res {
pattern = ["res/*", "r/*", "assets/*", "resources.arsc", "AndroidManifest.xml"]
ignoreChange = []
largeModSize = 100
}
packageConfig {
}
sevenZip {
zipArtifact = "com.tencent.mm:SevenZip:1.1.10"
// path = "/usr/local/bin/7za"
}
buildConfig {
keepDexApply = false
}
}
⑤打出基準包。
⑥安裝基準包的效果
⑥模擬熱修復,修改代碼,打出補丁包:注意,baseApk的信息一定要和生成的基準包的路徑名稱匹配,否則無法成功打出補丁包。
⑦在TinkerPatch Platform,創建你的應用,發布補丁:
⑧點擊拉取補丁,然后退出App,重新啟動,查看日志,成功拉取補丁的日志及App運行效果如下圖所示:
⑨查看后臺補丁監控信息,可以看到補丁下發的情況:
到此,接入Tinker就完成了。實際項目中,咱們的應用肯定是要在各大應用市場上線的,那么肯定要打多個渠道包,按照常規,我們是采用productFlavors實現的。假設項目要打10個渠道包,那么得針對每個渠道包,分開打10個補丁包,這顯然是不合理的。針對這種需求,Tinker官方給我們提供了多渠道打包的方案,如下圖:
所以我今天要介紹的就是官方推薦的一種方案:使用walle實現多渠道打包。
三、Tinker結合Walle多渠道打包的使用詳解
Walle(瓦力):是美團開源的Android Signature V2 Scheme 簽名下的新一代渠道包打包神器,跟gradle打包不一樣,walle是在APK Signature Block區塊添加自定義的渠道信息來生成渠道包,從而提高了渠道包生成效率,可以作為單機工具來使用,也可以部署在HTTP服務器上來實時處理渠道包Apk的升級網絡請求。 ---Walle的介紹
1.在項目根目錄的bulid,gradle文件中添加Walle插件的依賴:
classpath 'com.meituan.android.walle:plugin:1.1.6'
2.在當前App的 build.gradle 文件中apply這個插件,并添加上用于讀取渠道號的AAR
apply plugin: 'walle'
dependencies {
compile 'com.meituan.android.walle:library:1.1.6'
}
//配置插件
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.txt")
}
3.配置項具體解釋:
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 名
4.在App目錄下新建channel.txt配置所需要的渠道。具體內容格式詳見:渠道配置文件示例,支持使用#號添加注釋。
5.生成渠道包——在Android Studio的控制臺—Teminal輸入:
①生成所有渠道的渠道: gradlew clean assembleReleaseChannels
②生成某一個渠道:gradlew clean assembleReleaseChannels -PchannelList=baidu
③生成指定的多個渠道包 ./gradlew clean assembleReleaseChannels -PchannelList=baidu,xiaomi
Build成功后的效果如下圖:
生成的多渠道包的目錄如下圖:
6.針對多渠道打出適用所有渠道包的補丁包:使用tinkerPatchRelease打出補丁包,具體如下圖所示。
適用于所有渠道的補丁包的位置如下:
7.獲取渠道信息
String channel = WalleChannelReader.getChannel(this.getApplicationContext());
四、接入Tinker熱修復和多渠道打包后,項目線上APK的發布及補丁包發布的維護。
(1)多渠道APK的發布:
每次上線時,只需要執行上面生成渠道包的命令,打出多個渠道的APK即可,將各個渠道分發到各個應用市場即可。根據項目需求,可以通過獲取渠道信息,進行渠道統計。切記每次發布新版本時,一定要備份好bacApk目錄的文件,發布補丁的時候需要。因為一旦丟失,就失去了基準包的信息了,就無法打出相應基準包的補丁包了。
(2)補丁包的發布:
當線上APK出現bug需要修復時,在tinkerPatch.gradle中配置好你線上發布的基準包的信息(之前備份的基準包),使用tinkerPatchRelease打出補丁包,在TinkerPatch管理后臺下發補丁。具體如上步驟6.
五、接入時可能遇到的問題:
(1)接入Tinker時,打包的時候出現以下錯誤com.tencent.tinker.loader.TinkerRuntimeException: Tinker Exception:applicationLike must not be null.:是因為你的 tinkerPatch.gradle中配置 reflectApplication = false,但是你又沒有相應的改造你的Application類。本文介紹的是不改造我們的 Application 類接入Tinker,所以配置應該為:reflectApplication = ture。
(2)多渠道打包時,出現下面錯誤
- What went wrong:
Execution failed for task ':app:compileReleaseJavaWithJavac'.
Could not find tools.jar. Please check that D:\develop\JRE-New contains a valid JDK installation.
這個錯誤是因為在安裝JDK時,會安裝兩次,一次安裝JDK,一次安裝jre,因為第一次JDK的安裝就已經安裝了一個jre,而安裝時的提示會再次安裝一個jre。所以在第二次安裝jre時,先暫停,你需要將第一次安裝JDK的目錄下的一個/jre文件夾刪掉,然后在安裝另一個jre,這樣就可以了。再重新執行walle打包的命令,就能成功打出多渠道包了。
(3)Demo打開運行時,如果提示下面問題, Rebuild一下工程或者將implementation 'com.android.support:appcompat-v7:26.1.0'改成 implementation 'com.android.support:appcompat-v7:27.1.1'即可:
Error:Execution failed for task ':app:preDebugAndroidTestBuild'.
Conflict with dependency 'com.android.support:support-annotations' in project ':app'. Resolved versions for app (26.1.0) and test app (27.1.1) differ. See https://d.android.com/r/tools/test-apk-dependency-conflicts.html for details.
(4)執行多渠道打包命令(如gradlew clean assembleReleaseChannels)時,若提示如下BUILD FAILED的信息,Rebuild一下工程再執行打包命令即可正常打包。
- What went wrong:
Execution failed for task ':app:clean'.
Unable to delete file: C:\Users\18673\Desktop\TinkerPatchDemo-master\app\build\intermediates\manifests\full\debug\AndroidManifest.xml
知識儲備:
1、關于Dex
Dex是Android平臺上可執行文件的類型,是可以在Dalvik虛擬機上直接運行的文件格式。Java源代碼經過ADT(Android Development Tools)的復雜編譯后轉換成Dex文件,這是一個逐步優化的過程。Dex文件的指令碼就是Dalvik虛擬機專有的一套指令集,相比標準java的.class文件,它體積小,運行效率高。
3、關于虛擬機Dalvik
- 每一個Android應用都運行在一個Dalvik虛擬機實例里,而每一個虛擬機實例都是一個獨立的進程空間。虛擬機的線程機制,內存分配和管理,Mutex等等都是依賴底層操作系統而實現的。
- 所有Android應用的線程都對應一個Linux線程,虛擬機因而可以更多的依賴操作系統的線程調度和管理機制。
- 不同的應用在不同的進程空間里運行,加之對不同來源的應用都使用不同的Linux用戶來運行,可以最大程度的保護應用的安全和獨立運行。
4、關于ART模式
ART模式英文全稱為:Android runtime,谷歌Android 4.4系統新增的一種應用運行模式,與傳統的Dalvik模式不同,ART模式可以實現更為流暢的安卓系統體驗。
Android系統是以Linux系統為底層構建的。谷歌為了降低應用的開發難度在Linux底層之上構筑了一個名為“Dalvik”的虛擬機。
因為Dalvik虛擬機的存在,Android系統的開發者只需使用谷歌提供的SDK(軟件開發工具包)即可較為輕松的按照一套“規則”創建APP,不用顧忌硬件、驅動等問題,在每次執行應用的時候Dalvik虛擬機都會將程序的語言由高級語言編譯為機器語言,這樣當前設備才能夠運行這一應用。
ART模式與Dalvik模式最大的不同在于,在啟用ART模式后,系統在安裝應用的時候會進行一次預編譯,在安裝應用程序時會先將代碼轉換為機器語言存儲在本地,這樣在運行程序時就不會每次都進行一次編譯了,執行效率也大大提升。
本文完整Demo GitHub下載地址請戳:TinkerPatchDemo
本文參考:
Tinker源碼
TinkerPatch 接入及平臺使用文檔
Android 熱修復 Tinker接入及源碼淺析—hongyang
MultiDex與熱修復實現原理
Tinker加入Walle多渠道打包