【騰訊Bugly乾貨分享】微信Tinker的一切都在這裏,包括源碼(一)

本文來自於騰訊bugly開發者社區,非經做者贊成,請勿轉載,原文地址:http://dev.qq.com/topic/57ecdf2d98250b4631ae034bhtml

最近半年以來,Android熱補丁技術熱潮繼續爆發,各大公司相繼推出本身的開源框架。Tinker在最近也順利完成了公司的審覈,並不是常榮幸的成爲github.com/Tencent上第一個正式公開的項目。vue

回顧這半年多的歷程,這是一條跪着走完,坑坑不息之路。或許只有本身真正經歷過,深刻研究過, 纔會真正的明白android

熱補丁不是請客吃飯git

對熱補丁技術自己,仍是對使用者來講都是如此。它並不簡單,也有着本身的侷限性,在使用以前咱們須要對它有所瞭解。我但願經過分享微信在這歷程中的思考與經驗,能幫助你們更容易的決定是否在本身的項目中使用熱補丁技術,以及選擇什麼樣方案。github

熱補丁技術背景

熱補丁是什麼以及它的應用場景介紹,你們能夠參考文章微信Android熱補丁實踐演進之路算法

在筆者看來Android熱補丁技術應該分爲如下兩個流派:安全

  • Native,表明有阿里的Dexposed、AndFix與騰訊的內部方案KKFix;
  • Java, 表明有Qzone的超級補丁、大衆點評的nuwa、百度金融的rocooFix, 餓了麼的amigo以及美團的robust。

Native流派與Java流派都有着本身的優缺點,它們具體差別你們可參考上文。事實上歷來都沒有最好的方案,只有最適合本身的。微信

對於微信來講,咱們但願獲得一個「高可用」的補丁框架,它應該知足如下幾個條件:markdown

  1. 穩定性與兼容性;微信須要在數億臺設備上運行,即便補丁框架帶來1%的異常,也將影響到數萬用戶。保證補丁框架的穩定性與兼容性是咱們的第一要務;
  2. 性能;微信對性能要求也很是苛刻,首先補丁框架不能影響應用的性能,這裏基於大部分狀況下用戶不會使用到補丁。其次補丁包應該儘可能少,這關係到用戶流量與補丁的成功率問題;
  3. 易用性;在解決完以上兩個核心問題的前提下,咱們但願補丁框架簡單易用,而且能夠全面支持,甚至能夠作到功能發佈級別。

在「高可用」這個大前提下,微信對當時存在的兩個方案作了大量的研究:app

  1. Dexposed/AndFix;最大挑戰在於穩定性與兼容性,並且native異常排查難度更高。另外一方面,因爲沒法增長變量與類等限制,沒法作到功能發佈級別;
  2. Qzone;最大挑戰在於性能,即Dalvik平臺存在插樁致使的性能損耗,Art平臺因爲地址偏移問題致使補丁包可能過大的問題;

在2016年3月,微信爲了追尋「高可用」這個目標,決定嘗試搭建本身的補丁框架——Tinker。Tinker框架的演繹並非一蹴而就,它大體分爲三個階段,每一階段須要解決的核心問題並不相同。而Tinker v1.0的核心問題是實現符合性能要求的Dex補丁框架。

Tinker v1.0-性能極致追求之路

爲了穩定性與兼容性,微信選擇了Java流派。當前最大難點在於如何突破Qzone方案的性能問題,這時經過研究Instant Run的冷插拔與buck的exopackage給了咱們靈感。它們的思想都是全量替換新的Dex。

簡單來講,咱們經過徹底使用了新的Dex,那樣既不出現Art地址錯亂的問題,在Dalvik也無須插樁。固然考慮到補丁包的體積,咱們不能直接將新的Dex放在裏面。但咱們能夠將新舊兩個Dex的差別放到補丁包中,這裏咱們能夠調研的方法有如下幾個:

  1. BsDiff;它格式無關,但對Dex效果不是特別好,並且很是不穩定。當前微信對於so與部分資源,依然使用bsdiff算法;
  2. DexMerge;它主要問題在於合成時內存佔用過大,一個12M的dex,峯值內存可能達到70多M;
  3. DexDiff;經過深刻Dex格式,實現一套diff差別小,內存佔用少以及支持增刪改的算法。

如何選擇?在「高可用」的核心訴求下,性能問題也尤其重要。很是慶幸微信在當時那個節點堅定的選擇了自研DexDiff算法,這過程雖然有苦有淚,但也正是有它,纔有如今的Tinker。

一. DexDiff技術實踐

在不斷的深刻研究Dex格式後,咱們發現本身跳進了一個深坑,主要難點有如下三個:

  1. Dex格式複雜;Dex大體分爲像StringID,TypeID這些Index區域以及使用Offset的Data區域。它們有大量的互相引用,一個小小的改變可能致使大量的Index與Offset變化;
  2. dex2opt與dex2oat校驗;在這兩個過程系統會作例如四字節對齊,部分元素排序等校驗,例如StringID按照內容的Unicode排序,TypeID按照StringID排序…
  3. 低內存,快速;這要求咱們對Dex每一塊作到一次讀寫,沒法像baksmali與dexmerge那樣徹底結構化。

這不只要求咱們須要研究透Dex的格式,也要把dex2opt與dex2oat的代碼所有研究透。如今回想起來,這的確是一條跪着走完的路。與研究Dalvik與Art執行一致,這是經歷一次次翻看源碼,一次次編Rom查看日誌,一次次dump內存結構換來的結果。

下面以最簡單的Index區域舉例:

要想將從左邊序列更改爲右邊序列,Diff算法的核心在於如何生成最小操做序列,同時修正Index與Offset,實現增刪改的功能。

  1. Del 2;」b」元素被刪除,它對應的Index是2,爲了減小補丁包體積,除了新增的元素其餘一概只存Index;
  2. 「c」, 「d」, 「e」元素自動前移,無須操做;
  3. Addf(5); 在第五個位置增長」f」這個元素。

對於Offset區,因爲每一個Section可能有很是多的元素,這裏會更加複雜。最後咱們獲得最終的操做隊列,爲何DexDiff能夠作到內存很是少?這是由於DexDiff算法是每個操做的處理,它無需一次性讀入全部的數據。DexDiff的各項數據以下:

經過DexDiff算法的實現,咱們既解決了Dalvik平臺的性能損耗問題,又解決了Art平臺補丁包過大的問題。但這套方案的缺點在於佔Rom體積比較大,微信考慮到移動設備的存儲空間提高比較快,增長几十M的Rom空間這個代價能夠接受。

二. Android N的挑戰

信心滿滿上線後,卻很快收到華爲反饋的一個Crash:

並且這個Crash只在Android N上出現,在當時對咱們震動很是大,難道Android N不支持Java方式熱補丁了?難道這兩個月的辛苦都白費了嗎?一切想象都蒼白無力,只有繼續去源碼裏面找緣由。

在以前的基礎上,這一塊的研究並無花太多的時間,主要是Android N的混合編譯模式致使。更多的詳細分析可參考文章Android N混合編譯與對熱補丁影響解析

三. 廠商OTA的挑戰

剛剛解決完Android N的問題,還在沉醉在本身的勝利的愉悅中。前線很快又傳來噩耗,小米反饋開發版的一些用戶在微信啓動時黑屏,甚至ANR.

當時第一反應是不可能,全部的DexOpt操做都是放到單獨的進程,爲何只在Art平臺出現?爲何小米開發版用戶反饋比較多?通過分析,咱們發現優化後odex文件存在有效性的檢查:

  • Dalvik平臺:modtime/crc…
  • Art平臺: checksum/image_checksum/image_offset…

這就很是好理解了,由於OTA以後系統image改變了,odex文件用到image的偏移地址極可能已經錯誤。對於ClassN.dex文件,在OTA升級系統已完成從新dex2oat,而補丁是動態加載的,只能在第一次執行時同步執行。

這個耗時可能高達十幾秒,黑屏甚至ANR也是很是好理解。那爲何只有小米用戶反饋比較多呢?這也是由於小米開發版每週都會推送系統升級的緣由。

在當時那個節點上,咱們從新的審視了全量合成這一思路,再次對方案原理自己產生懷疑,它在Art平臺上面帶來了如下幾個代價:

  1. OTA後黑屏問題;這裏或許能夠經過lLoading界面實現,但並非很好的方案;
  2. Rom體積問題;一個10M的Dex,在Dalvik下odex產物只有11M左右,但在Art平臺,能夠達到30多M;
  3. Android N的問題;Android N在混合編譯上努力,被補丁全量合成機制所廢棄了。這是由於動態加載的Dex,依然是全量編譯。

回想起來,Qzone方案它只把須要的類打包成補丁推送,在Art平臺上可能致使補丁很大,但它確定比全量合成10M的Dex少不少不少。在此咱們提出分平臺合成的想法,即在Dalvik平臺合成全量Dex,在Art平臺合成須要的Dex

DexDiff算法已經很是複雜,事實上要實現分平臺合成並不容易。

主要難點有如下幾個方面:

  • small dex的類收集;什麼類應該放在這個小的Dex中呢?
  • ClassN處理;對於ClassN怎麼樣處理,可能出現類從一個Dex移動到另一個Dex?
  • 偏移二次修正; 補丁包中的操做序列如何二次修正?
  • Art.info的大小; 爲了修正偏移所引入的info文件的大小?

慶幸的是,面對困難咱們並無畏懼,最後實現了這一套方案,這也是其餘全量合成方案所不能作到的:

  1. Dalvik全量合成,解決了插樁帶來的性能損耗
  2. Art平臺合成small dex,解決了全量合成方案佔用Rom體積大, OTA升級以及Android N的問題
  3. 大部分狀況下Art.info僅僅1-20K, 解決因爲補丁包可能過大的問題

事實上,DexDiff算法變的如此複雜,怎麼樣保證它的正確性呢?微信爲此作了如下三件事情:

  1. 隨機組成Dex校驗,覆蓋大部分case;
  2. 微信200個版本的隨機Diff校驗, 覆蓋平常使用狀況;
  3. Dex文件合成產物有效性校驗,即便算法出現問題,也只是編譯不出補丁包。

每一次DexDiff算法的更新,都須要通過以上三個Test才能夠提交,這樣DexDiff的這套算法已完成了整個閉環。

四. 其餘技術挑戰

在實現過程,咱們還發現其餘的一些問題:

  1. Xposed等微信插件; 市面上有各類各樣的微信插件,它們在微信啓動前會提早加載微信中的類,這會致使兩個問題:

    a. Dalvik平臺:出現Class ref in pre-verified class resolved to unexpected implementation的crash;

    b. Art平臺:出現部分類使用了舊的代碼,這可能致使補丁無效,或者地址錯亂的問題。

    微信在這裏的處理方式是若crash時發現安裝了Xposed,即清除並再也不應用補丁。

    1. Dex反射成功可是不生效;部分三星android-19版本存在Dex反射成功,但出現類重複時,查找順序始終從base.apk開始。
      微信在這裏的處理方式是增長Dex反射成功校驗,具體經過在框架中埋入某個類的isPatch變量爲false。在補丁時,咱們自動將這個變量改成true。經過這個變量最終的數值,咱們能夠知道反射成功與否。

Tinker v1.0總結

一. 關於性能

經過Tinker v1,0的努力,咱們解決了Qzone方案的性能問題,獲得一個符合「高可用」性能要求的補丁框架。

  • 它補丁包大小很是少,一般都是10k之內;
  • 對性能幾乎沒有影響, 2%的性能影響主要緣由是微信運行時校驗補丁Dex文件的md5致使(雖然文件在/data/data/目錄,微信爲了更高級別的安全);
  • Art平臺經過革命性的分平臺合成,既解決了地址偏移的問題,佔Rom體積與Qzone方案一致。

二. 關於成功率

也許有人會質疑微信成功率爲何這麼低,其餘方案都是99%以上。事實上,咱們的成功率計算方式是:

應用成功率= 補丁版本轉化人數/基準版本安裝人數

即三天後,94.1%的基礎版本都成功升級到補丁版本,因爲基礎版本人數也是持續增加,同時可能存在基準或補丁版本用戶安裝了其餘版本,因此本統計結果應略爲偏低,但它能現實的反應補丁的線上整體覆蓋狀況。

事實上,採用Qzone方案,3天的成功率大約爲96.3%,這裏仍是有不少的優化空間。

三. Tinker v2.0-穩定性的探尋之路

在v1.0階段,大部分的異常都是經過廠商反饋而來,Tinker並無解決「高可用」下最核心的穩定性與兼容性問題。咱們須要創建完整的監控與補丁回退機制,監控每個階段的異常狀況。這也是Tinker v2.0的核心任務,因爲邊幅問題這部份內容將放在下一篇文章。


關注Tinker,來Github給咱們star吧

https://github.com/Tencent/tinker

更多精彩內容歡迎關注bugly的微信公衆帳號:

騰訊 Bugly是一款專爲移動開發者打造的質量監控工具,幫助開發者快速,便捷的定位線上應用崩潰的狀況以及解決方案。智能合併功能幫助開發同窗把天天上報的數千條 Crash 根據根因合併分類,每日日報會列出影響用戶數最多的崩潰,精準定位功能幫助開發同窗定位到出問題的代碼行,實時上報能夠在發佈後快速的瞭解應用的質量狀況,適配最新的 iOS, Android 官方操做系統,鵝廠的工程師都在使用,快來加入咱們吧!

相關文章
相關標籤/搜索