原理及介紹
現(xiàn)階段,Android熱補丁技術應該是分為以下兩個流派:
- Native:代表有阿里的Dexposed、AndFix與騰訊的內部方案KKFix;
- Java:代表有Qzone的超級補丁、大眾點評的nuwa、美團的robust、百度金融的rocooFix, 餓了么的amigo。
Native流派與Java流派都有著自己的優(yōu)缺點,它們具體差異可參考此文:微信Android熱補丁實踐演進之路 。微信Tinker屬于Java流派。核心思想是利用DexDiff算法對比差異生成Patch補丁包,分平臺合成,全量替換新的Dex。
校驗Dex
Tinker它合并完成時完全使用了新的Dex,并且實現(xiàn)了分平臺合成。這樣既不出現(xiàn)Art地址錯亂的問題,在Dalvik環(huán)境也無須插樁。當然考慮到補丁包的體積,我們不能直接將新的Dex放在里面。但可以將新舊兩個Dex的差異放到補丁包中,這里主要調研的方法有以下幾個:
1.BsDiff;它格式無關,但對Dex效果不是特別好,而且非常不穩(wěn)定。當前微信對于so與部分資源,依然使用bsdiff算法;
2.DexMerge;它主要問題在于合成時內存占用過大,一個12M的dex,峰值內存可能達到70多M;
3.DexDiff;通過深入Dex格式,實現(xiàn)一套diff差異小,內存占用少以及支持增刪改的算法。
DexDiff算法已經非常復雜,事實上要實現(xiàn)分平臺合成并不容易。主要難點有以下幾個方面:
- small dex的類收集;什么類應該放在這個小的Dex中呢?
- ClassN處理;對于ClassN怎么樣處理,可能出現(xiàn)類從一個Dex移動到另外一個Dex?
- 偏移二次修正; 補丁包中的操作序列如何二次修正?
- Art.info的大小; 如何修正偏移所引入的info文件的大小?
微信團隊最后實現(xiàn)了這一套方案,這也是其他全量合成方案所不能做到的:
- Dalvik全量合成,解決了插樁帶來的性能損耗;
- Art平臺合成small dex,解決了全量合成方案占用Rom體積大, OTA升級以及Android N的問題;
- 大部分情況下Art.info僅僅1-20K, 解決由于補丁包可能過大的問題;
事實上,DexDiff算法變的如此復雜,怎么樣保證它的正確性呢?微信為此做了以下三件事情:
- 隨機組成Dex校驗,覆蓋大部分case;
- 微信200個版本的隨機Diff校驗, 覆蓋日常使用情況;
- Dex文件合成產物有效性校驗,即使算法出現(xiàn)問題,也只是編譯不出補丁包。
每一次DexDiff算法的更新,都需要經過以上三個Test才可以提交,這樣DexDiff的這套算法已完成了整個閉環(huán)。
DexDiff 算法
它屬于二路歸并算法,對Dexdiff算法有興趣研究的童鞋可以看看這里:Tinker Dexdiff算法解析
算法過程描述:
1.首先我們需要將新舊內容排序,這需要針對排序的數(shù)組進行操作。
2.新舊兩個指針,在內容一樣的時候 old、new 指針同時加1,在 old 內容小于 new 內容(注:這里所說的內容比較是單純的內容比較比如'A'<'a')的時候 old 指針加1 標記當前 old 項為刪除。
3.在 old 內容大于 new 內容 new 指針加1, 標記當前 new 項為新增。
下面是算法執(zhí)行的簡單過程:
------old-----
11 foo2
12 foo5
13 hello dodola
14 hello dodola1
15 hello dodola2
16 hello dodola5
17 out
18 println
------new-----
11 foo3
12 foo5
13 hello dodola1
14 hello dodola3
15 hello dodola_modify
16 out
17 println
對比的old cursor 和 new cursor 指針的改變以及操作判定,判定過程如下
old_11 new_11 cmp <0 del
old_12 new_11 cmp >0 add
old_12 new_12 cmp =0 no
old_13 new_13 cmp <0 del
old_14 new_13 cmp =0 no
old_15 new_14 cmp <0 del
old_16 new_14 cmp >0 add
old_16 new_15 cmp <0 del
old_17 new_15 cmp >0 add
old_17 new_16 cmp =0 no
old_18 new_17 cmp =0 no
break;
進入下一步過程
可以確定的是刪除的內容肯定是從 old 中的 index 進行刪除的 添加的內容肯定是從 new 中的 index 中來的,按照這個邏輯我們可以整理如下內容。
old_11 del
new_11 add
old_13 del
new_14 add
old_15 del
new_15 add
old_16 del
到這一步我們需要找出替換的內容,很明顯替換的內容就是從 old 中 del 的并且在 new 中 add 的并且 index 相同的i tem,所以這就簡單了
old_11 replace
old_13 del
new_14 add
old_15 replace
old_16 del
ok,到這一步我們就能判定出兩個dex的變化了,很機智的算法。
運行時替換PathClassLoader
事實上,App image中的class是插入到PathClassloader中的ClassTable中。假設我們完全廢棄掉PathClassloader,而采用一個新建Classloader來加載后續(xù)的所有類,即可達到將cache無用化的效果。
需要注意的問題是我們的Application類是一定會通過PathClassloader加載的,所以我們需要將Application類與我們的邏輯解耦,這里方式有兩種:
1.采用類似instant run的實現(xiàn);在代理application中,反射替換真正的application。這種方式的優(yōu)點在于接入容易,但是這種方式無法保證兼容性,特別在反射失敗的情況,是無法回退的。
2.采用代理Application實現(xiàn)的方法;即Application的所有實現(xiàn)都會被代理到其他類,Application類不會再被使用到。這種方式沒有兼容性的問題,但是會帶來一定的接入成本。
其他的一些問題:
由于原理與系統(tǒng)限制,Tinker有以下已知問題:
- Xposed等微信插件; 市面上有各種各樣的微信插件,它們在微信啟動前會提前加載微信中的類,這會導致兩個問題:
1.Dalvik平臺:出現(xiàn)Class ref in pre-verified class resolved to unexpected implementation的crash;
2.Art平臺:出現(xiàn)部分類使用了舊的代碼,這可能導致補丁無效,或者地址錯亂的問題。
微信在這里的處理方式是:若crash時發(fā)現(xiàn)安裝了Xposed,則立即清除并不再應用補丁。
Dex反射成功但是不生效;
部分三星android-19版本存在Dex反射成功,但出現(xiàn)類重復時,查找順序始終從base.apk開始。 微信在這里的處理方式是增加Dex反射成功校驗,具體通過在框架中埋入某個類的isPatch變量為false。在補丁時,我們自動將這個變量改為true。通過這個變量最終的數(shù)值,則可以知道反射成功與否。不支持部分三星android-21機型,加載補丁時會主動拋出"TinkerRuntimeException:checkDexInstall failed";
T不支持修改AndroidManifest.xml,Tinker不支持新增四大組件;
由于Google Play的開發(fā)者條款限制,不建議在GP渠道動態(tài)更新代碼;
在Android N上,補丁對應用啟動時間有輕微的影響;
-
對于資源替換,不支持修改remoteView。例如transition動畫,notification icon以及桌面圖標。
2017-8-4 更新 :
1.7.11版本或以下版本,項目中若使用了RxJava,在調用特殊操作符時會報如下錯誤:
java.lang.VerifyError: Rejecting class foc because it failed compile-time verification (declaration of 'foc' appears in /data/user/0/com.xxx.android/tinker/patch-91b25513/dex/classes5.dex.jar)
at euy.zipArray(SourceFile:4567)
at euy.zip(SourceFile:3883)
at euy.zipWith(SourceFile:13590)
at aqi.c(SourceFile:67)
...
具體原因:RxJava里的zip特殊操作符邏輯加上art對aput這個指令的校驗方式湊在一起,導致Crash 。Tinker 官方GitHub 項目 Issue 鏈接地址:https://github.com/Tencent/tinker/issues/491
參考文獻:
微信移動技術團隊GitHub博客地址: WeMobileDev/article
Tinker GitHub Wiki :tinker/wiki
Tinker Dexdiff算法解析 : Dexdiff算法解析
Android_N混合編譯與對熱補丁影響:WeMobileDev/article/Android_N