做者:趙裕, 騰訊移動客戶端開發
商業轉載請聯繫騰訊WeTest得到受權,非商業轉載請註明出處。
原文連接:http://wetest.qq.com/lab/view/338.htmlhtml
本文探討了Android熱修復技術的發展脈絡,現狀及其將來。算法
熱修復技術在近年來飛速發展,尤爲是在InstantRun方案推出以後,各類熱修復技術競相涌現。國內大部分紅熟的主流APP都擁有本身的熱修復技術,像手淘、支付寶、QQ、餓了麼、美團等等。數組
目前能蒐集到的資料,大多簡單羅列每一個方案的特色並進行橫向比較,而其中技術發展的脈絡每每被掩蓋了。熱修復技術從何而來,又將往何處去?在這些資料中都找不到答案。安全
我認爲,蜻蜓點水地看一遍各家的熱修復方案並不能找到答案,因此寫下本文,但願從一個不一樣的角度來了解熱修復技術,權當拋磚引玉,若有不足,歡迎指正。微信
代碼熱修復是最多見,也是熱修復中最重要的部分,由於程序錯誤每每都是代碼邏輯的錯誤。最初的熱修復方案也僅支持代碼熱修復。代碼熱修復分兩個流派,即騰訊系的類加載方案和阿里系的底層替換方案,前者須要重啓應用但卻能修復大部分錯誤,後者及時生效卻只能做方法內的修改。下面詳細介紹。工具
一、Qzone
Qzone的超級熱修復方案是業界最先的熱修復方案之一,原理簡單而巧妙,影響深入而久遠,在此簡單介紹。Android類加載的源碼以下:
性能
能夠看出當有多個dex文件時,他們會組成一個有序數組,按順序加載,而對於一個已經加載的Class是不會再次加載的,由此得出熱修復方案:把須要修復的類打包成一個dex文件下發,並在APP啓動時經過反射,將這個dex文件放在dexElements的最前面,這樣修復了的Class就會比有Bug的Class優先加載了。以下圖所示:
測試
但在實現過程當中,會遇到unexpected DEX problem異常,Qzone方案爲了解決這個問題採用了插樁的策略來規避這個異常。實際上,Android系統的檢查和優化都是有其意義的,所以這種方法在Dalvik和Art上都會遇到問題。優化
● 在Dalvik虛擬機,APP在安裝的時候會被執行dexopt操做,同一個dex文件內的Class會被打上CLASS_ISPREVERIFIED標誌,而補丁包中的類並無打上此標誌,所以拋出異常。解決方法就是在第一次打包APK時讓全部類都引用另外一個dex文件中的類,這樣全部的類始終不會打上CLASS_ISPREVERIFIED標誌,所以補丁包能夠順利加載,可是Dalvik虛擬機在檢測到一個類未打上CLASS_ISPREVERIFIED以後會再次在類加載的時候進行dexopt相關的操做,若是一次性加載不少類,速度將明顯變慢。spa
● 在Art虛擬機,dex文件最終會編譯成本地機器碼,在dex2oat時fast *已經將各個類的地址寫死,若補丁包中的類出現字段或者方法的修改,會出現內存地址錯亂,解決辦法是將這個類的父類和調用這個類的類都加入補丁包。但這樣會致使補丁包急劇增大。(實際上要理解清楚這個問題須要熟悉Dalvik和Art的完整流程,並不是三言兩語能解釋清楚)
這兩個問題均可以解決,但都要付出一些代價:類加載速度或者補丁包大小。
二、Tinker
若是Qzone沒有上面兩個缺陷,或許就不會有Tinker了。對於微信這樣一個對性能有極高要求的產品來講,Qzone的缺點會被無限放大。在參考Instant Run的冷插拔與buck的exopackage後,Tinker採用了全量替換的策略。全量替換能夠避免插樁和地址寫死問題,可是補丁包會很大,所以能夠在新舊兩個Dex的差別放在補丁包中,下發到移動端後再在本地合成完整的dex文件。
實際上,Tinker保留了Qzone最核心的東西:反射修改dexElements。不管是插入仍是替換,本質都是利用了類加載的特色。因爲須要下發的全量補丁包體積過大,Tinker採用了後臺求diff,下發diff文件,移動端合成全量包的策略。
若是僅此而已,只要有diff/patch算法,就能夠開發Tinker了。實際上,確實如此。而Tinker第二個創新之處就是採用了自研的DexDiff算法,大大優化了下發差別包的大小。
阿里的Andfix熱修復方案是底層替換方案的表明,與Qzone和Tinker的思想徹底不一樣,Andfix經過修改一個方法的入口地址來達到修復。以Dalvik虛擬機爲例,Andfix的核心代碼以下:
其實就是修改了方法包括入口地址在內的每一項數據的地址,使之指向一個新的方法。在後臺,使用Andfix提供的apkpatch工具,能夠獲得補丁文件out.apatch,這個文件記錄了哪些方法須要修改,以及修改後的方法。Andfix效果:
(注意我一直在點擊,下發補丁後發生了變化……)
除了代碼熱修復,資源熱修復也很常見。各大主流方案在資源修復的實現上大多參考了InstantRun的實現方式,所以本章節先討論了InstantRun,再分析了基於InstantRun所實現的熱修復。
InstantRun在AndroidStudio2.0.0中引入。
InstantRun包括代碼修復和在資源修復,資源修復的核心代碼:
其實作了兩件事:
關鍵是要熟悉Android相關源碼,才能肯定哪些字段是須要更新引用的。經過以上兩步便可實現資源替換。
將InstantRun的monkeyPatchExistingResource方法引入咱們的代碼就能夠實現資源熱修復,效果以下:
so庫的修復本質是對native方法的修復和替換,和類加載方案相似,能夠把補丁so庫的路徑插入到nativeLibraryDirectories數組的最前面,使得優先加載補丁庫而不是原來的庫來達到修復目的。在此不作贅述。
最初Qzone就須要在Dalvik平臺進行插樁,Tinker一樣也是分平臺合成(在Dalvik平臺合成全量Dex,在Art平臺合成須要的小Dex),而阿里的Andfix做爲底層熱修復方案,不只要面對兩種虛擬機平臺,甚至要爲不一樣Android版本編寫一套替換邏輯,以下:
加載了補丁包的程序本質仍是未編譯的程序,只是兩個已編譯程序的結合體,因爲Java的編譯過程對於咱們是透明,因此咱們一不當心就會引入錯誤,並且這種錯誤十分隱蔽。在使用類加載方案時因爲仍是在Java層,可能不那麼容易犯錯,但使用Andfix等底層熱修復方案時卻老是防不勝防。
好比,Java在編譯匿名內部類時會編譯成頂級類,命名方式爲ClassName$n,其中n爲匿名內部類出現的順序,因此在第i個匿名內部類前面添加匿名內部類就會致使ClassName$i#methodName變成ClassName$i+1#methodName,即一個方法的地址發生改變。再好比,Java的泛型編譯可能會在編譯期引入新的方法,也會致使Andfix的異常。
由於編譯過程是透明的,因此熱修復後的程序不能代替修復問題後從新編譯出來的程序,即熱修復後程序的安全性是得不到保證的。
Qzone時期插樁影響了類加載的速度,Tinker的DexDiff算法粒度過細、實現複雜,致使性能消耗嚴重,Andfix使用場景有限、兼容性差,此外美團的Robust、餓了麼的Amigo等也都各有限制。Android熱修復技術雖然百花齊放,但卻並無哪一種方案可以解決全部問題,統一當前的局面。而最近阿里又推出了Sophix,針對各類類型的修復又作了深度的優化,雖然沒有開源代碼,可是發佈了《深刻探索Android熱修復技術原理》,引發Android社區的關注,其統一各類熱修復方案的意圖也十分明顯。
從Qzone到Tinker,從Andfix到Sophix均可以看出來,熱修復技術還在不斷上升發展,每一次新方案的推出都是對原有方案的超越。但目前來看,阿里並未打算開源Sophix,而Tinker2.0仍然在路上,熱修復技術在性能、兼容、開發透明方面仍然有不少不足,因此不能僅僅知足於瞭解已有方案,還要深刻源碼去理解原理,更要對業界最新進展保持關注。
參考:
一、安卓App熱補丁動態修復技術介紹
二、微信Tinker的一切都在這裏,包括源碼(一)
三、alibaba/AndFix: AndFix is a library that offer hot-fix for Android App.
四、Instant Run: How
五、微信Android熱補丁實踐演進之路
六、Tencent/tinker: Tinker is a hot-fix solution library for Android, it ……
騰訊WeTest兼容性測試團隊積累了10年的手遊測試經驗,旨在經過制定針對性的測試方案,精準選取目標機型,執行專業、完整的測試用例,來提早發現遊戲版本的兼容性問題,針對性地作出修正和優化,來保障手遊產品的質量。目前該團隊已經支持全部騰訊在研和運營的手遊項目。
iPhone8/iPhoneX新機即將同步上線,歡迎進入:http://wetest.qq.com/product/expert-compatibility-testing 使用專家兼容測試服務。 WeTest兼容性測試團隊期待與您交流!You Create,We Test!
若是對使用當中有任何疑問,歡迎聯繫騰訊WeTest企業qq:800024531