【騰訊Bugly乾貨分享】微信熱補丁Tinker的實踐演進之路

来源:http://www.cnblogs.com/bugly/archive/2016/08/19/5787824.html
-Advertisement-
Play Games

Tinker 是微信官方的 Android 熱補丁解決方案,它支持動態下發代碼、So庫以及資源,讓應用能夠在不需要重新安裝的情況下實現更新。這裡大致介紹 Tinker 的實現原理,當時遇到的各種坑以及對它各個方面性能的優化工作。 ...


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

Dev Club 是一個交流移動開發技術,結交朋友,擴展人脈的社群,成員都是經過審核的移動開發工程師。每周都會舉行嘉賓分享,話題討論等活動。

本期,我們邀請了騰訊WXG Android開發工程師——張紹文,為大家分享《微信熱補丁Tinker的實踐演進之路》


分享內容簡介:
Tinker 是微信官方的 Android 熱補丁解決方案,它支持動態下發代碼、So庫以及資源,讓應用能夠在不需要重新安裝的情況下實現更新。這裡大致介紹 Tinker 的實現原理,當時遇到的各種坑以及對它各個方面性能的優化工作。

內容大體框架:

  1. 當前各種熱補丁框架的比較以及 Tinker 的設計目標
  2. Tinker的實踐演進
  3. Tinker在實現中遇到的困難

下麵是本期分享內容整理


hello,大家好。我是張紹文,目前在微信主要負責 Android 的性能優化以及終端質量平臺相關工作。

下麵開始我們今天的分享。

1. 當前各種熱補丁框架對比 & Tinker 的設計目標

熱補丁技術是當前非常熱門的 Android 開發技術,其中比較出名的方案有支付寶的 AndFix以及 QZone 的超級熱補丁方案。

微信大約在2015年6月開始嘗試應用,經過研究與嘗試現有的各個方案,我們發現它們都有著自身的一些局限性。我們最終採用不同於它們的技術方案,自研微信熱補丁開源框架 Tinker。

下麵我們先來講講先有框架的一些局限性。

1.1 AndFix

Andfix 是阿裡推出的開源框架,它在 github 的地址是:

https://github.com/alibaba/AndFix

它的技術原理如下圖:它採用 native hook 的方式,這套方案直接使用 dalvik_replaceMethod 替換 class 中方法的實現。

它的缺點主要包括以下幾個:

  1. 相容性不佳;由於它採用 native 替換的方式,在 github Issue 中也有大量崩潰的反饋;

  2. 成功率不高;不支持修改 inline 方法,不支持修改方法參數超過8個或參數中帶有 long, double 或者 float。跟一些使用 Andfix 的產品討論過,它們的成功率不超過40%;

    原因:只替換了 DexCache 中的 ArtMethod 結構體,對於 Art 中一些 compiledCode 是直接通過 bx 過去

  3. 開發不透明;由於它還不支持增加 filed,我們需要為了補丁而補丁,無法採用這個技術發佈需求。

    Andfix 的好處是可以立刻生效,但它可以支持的補丁場景非常有限,僅僅可以使用它來修複特定問題。

    所以我們不考慮採用這個方案。

    1.2 Qzone 超級補丁方案

    現在我們講講 Qzone 超級補丁方案,在騰訊內部已開源。

    這個方案使用 classloader 的方式,能實現更加友好的類替換。而且這與我們載入 Multidex 的做法相似,能基本保證穩定性與相容性。

    它主要的面臨問題有兩個:

  4. 為瞭解決 unexpected DEX problem 異常,而採用插樁的方式給所有類插入不會真正運行的代碼,防止類打上 preverify 標誌。

    採用插樁導致所有類都非 preverify,導致上圖中的 verify 與 optimize 操作會在載入類時觸發。這會有一定的性能損耗,微信分別採用插樁與不插樁兩種方式做過兩種測試,一是連續載入700個50行左右的類,一是統計微信整個啟動完成的耗時。

  5. 在 art 平臺,若補丁中的類出現 Field、Method 或 Interface 變化,可能會導致出現記憶體地址錯亂的問題。為瞭解決這個問題,我們最後補丁中的類要有以下規則:
    a. 修改跟新增的 class;
    b. 若 class 有 field,method 或 interface 數量變化,它們所有的子類;
    c. 若 class 有 field,method 或 interface 數量變化,它們以及它們所有子類的調用類。如果採用 ClassN 方式,即需要多個 dex 一起處理。

    Qzone 的方案最為簡單,而且開發透明,補丁的成功率也是非常高的。

    但由於微信對於運行性能以及補丁大小都比較敏感,我們最終也沒有採用這套方案。

    1.3 Tinker 的設計目標

    那麼微信希望的是一套怎麼樣的熱補丁框架呢,我們認為主要的目標有以下幾個:

  6. 開發透明;開發者無需關心是否在補丁版本,他可以隨意修改,不由框架限制;

  7. 性能無影響;補丁框架不能對應用帶來性能損耗;
  8. 完整支持;支持代碼,So 庫以及資源的修複,可以發佈功能。
  9. 補丁大小較小;補丁大小應該儘量的小,提高升級率。
  10. 穩定,相容性好;保證微信的數億用戶的使用,儘量減少反射;

2. 3. Tinker 的實踐演進

現在我們來講講微信熱補丁框架 Tinker 的實現,目前在騰訊內部已開源。

它的名字來至 Dota 中的地精修補匠,我們希望發版本可以像它一樣做到無限刷新。

Tinker 的方案來源 gradle 編譯的 instant run 與 buck 編譯的 exopackage。它們的思想都是全量替換新的 Dex。即我們完全使用了新的 Dex,那樣既不出現 Art 地址錯亂的問題,在 Dalvik 也無須插樁。

但是 instant run 針對的是編譯期,它可以直接將最後生成的所有變化都直接拷到手機端。對於線上方案,這肯定是不可行的。所以當前核心問題是找到合適的,使補丁結果更小的差分演算法。

微信首先 demo 中採用的是 bsdiff,它無關文件格式,但對於dex效果不是特別好,而且非常不穩定。當前微信對於 so,依然使用 bsdiff 演算法。

然後我們想到 dexmerge 演算法,把修改跟新增的類通過 dexmerge 方式與原來的 dex 合併,從而得到最終的完整 Dex。

經過實踐,dexmerge 的核心問題有兩個:

  1. 無法刪除 class;導致在 Dalvik 平臺會出現載入類重覆的情況,這要求我們只能採用 miniloader 載入方案來避免;
  2. 合成時記憶體占用過大;dexmerge 庫使用場景在 pc,它沒有太多的考慮記憶體問題。它的峰值記憶體可以達到輸入 dex 的大小的4倍-6倍。一個12M的 dex,峰值記憶體可能達到70多M。

    最後我們決定基於 dex 的格式,自研出一種 Dexdiff 演算法,它需要達到以下目標;

  3. diff 結果小;

  4. 合成過程占用記憶體小;
  5. 支持刪除、新增、修改 dex 中的 class。

    這裡面主要的原理是深度利用原來 dex 中的信息,對於 dex 的每一個 section 做處理。這塊在今天不再深入,感興趣的同學可以交流。

    記憶體方面 dexdiff 峰值記憶體是 dex 的兩倍左右,達到預期的結果。

    對於微信熱補丁的更多信息,可以閱讀我之前發的一篇文章。

    微信Android熱補丁實踐演進之路

然後我們來看看 Tinker 的框架設計,它主要包括以下幾部分:

  1. 補丁合成;這些都在單獨的 patch 進程工作,這裡包括 dex,so 還有資源,主要完成補丁包的合成以及升級;
  2. 補丁的載入;如果通過反射系統載入我們合成好的 dex,so 與資源;
  3. 監控回調;在合成與載入過程中,出現問題及時回調;
  4. 版本管理; Tinker 支持補丁升級,甚至是多個補丁不停的切換。這裡我們需要保證所有進程版本的一致性;
  5. 安全校驗;無論在補丁合成還是載入,我們都需要有必要的安全校驗。

    在微信中,我們為 Tinker 框架加入了100多個實時上報,監控著在每個過程可能出現的問題:

    3.Tinker 在實現中遇到的困難

    接下來我們來看看在開發 Tinker 過程中,遇到的一些問題:

    1. 廠商 OTA;

    對於 Art 平臺,dex2oat 時間較長。特別是廠商 OTA 之後,所有動態載入的代碼都需要重新執行 dex2oat。這是因為 boot image 已經改變,但是系統在升級時只會給 ClassN.dex 重新 OTA。

    對於補丁 dex 會出現主進程同步執行 dex2oat,這個時間非常久,很有可能會出現 ANR,對於小米等一些產品的開發板更是如此。這也是我們現在努力在實現分平臺合成的原因,即在 Art平臺,只合成規則下需要的 class。只要不是全量替換,重新 dex2oat 的時間是可以接受的。

    2. Android N 混合編譯導致補丁機制失效

    這塊花了一定的時間重新梳理了 Android N art 的代碼,詳細的分析可以查看之前我發的一篇文章。

    Android N混合編譯與對熱補丁影響解析

    3. Dex 反射成功但是不生效;

    開始的時候,我們載入補丁 dex 採用的是 makedexElement 的方式。但是發現大約有幾十萬台機器,補丁載入成功了,但是使用的還是舊版本的代碼。某些機器類似三星 s6 502系統,儘管反射 pathList 成功,查找順序依然以 base.apk 優先。

    這裡採取的解決方法是類似 instant run,採用反射 parent classloader 的方式。這裡不得不提,instant run 的 increaseClassLoader 實現非常精妙。

    4. Xposed 等微信插件;

    市面上有各種各樣的微信插件,它們在微信啟動前會提前載入微信中的類,這會導致兩個問題:

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

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

    它們根本的原因都是Xposed反射調用,提前導入了我們的某些類。

    事實上,由於補丁使用不當或者其他問題,我們的確需要有一個安全模式。即在應用啟動不起來或多次 crash 時,進入補丁清理或者升級的流程。

    結語

    也許有人覺得 Tinker 過於臃腫,過於複雜。這是因為熱補丁並不是僅僅載入一個 dex 或 so 文件,事實上它要關心的細節有很多。進程的一致性,控制可修改類的範圍,版本的管理,擴展性等等。

    Tinker 的未來規劃是真正的開源出去,大約下周會提交分享平臺合成以及資源相關的所有代碼。然後等公司的開源審計結束後將在 github 開源,歡迎大家接入 Tinker 內測,給我們更多的意見。

    由於時間有限,今天的分享就到這裡。對於 So,資源的合成方式,dexdiff 的技術細節,若大家感興趣可以與我們交流。

    互動問答環節

    Q1:請教下 patch 進程和主進程是怎麼通信的?

    是通過 intent service 通信的,主進程一個接受補丁結果的 intent service,patch 進程是一個接受補丁請求的 intent service

    Q2:“分平臺合成”沒聽太明白,能再仔細說下麽?

    分平臺合成就是在 Dalvik 平臺,我們合成全量的 dex,這可以避免我們插樁的要求。

    在 Art 平臺,我們只合成上述三個條件下的類:

    a. 修改跟新增的 class;
    b. 若 class 有 field,method 或 interface 數量變化,它們所有的子類;
    c. 若 class 有 field,method 或 interface 數量變化,它們以及它們所有子類的調用類。如果採用 ClassN 方式,即需要多個 dex 一起處理。

    這裡的難點是同一份 diff 代碼,可以做到不同的合成方式。

Q3:對於內部空間不足引起的 patch 失敗現在有什麼好的解決辦法?

對於我們的方案,空間占用有可能比較大,我們解決的方法有兩個:

  1. 在 patch 之前提前檢查用戶的剩餘空間,如果用戶剩餘空間過少,即不嘗試。
  2. 若本次失敗,我們會有回調,然後我們會定期重試三次。

你也可以在這裡採用提示用戶清理空間。Tinker 框架是可以高度定製化的。

Q4:對於替換 classloader 失敗後再用 MultiDex install 這種方案有什麼考慮?

有的,對於替換失敗的話,的確會回退到類似 Multidex install 方式的

Q5:目前微信對熱補丁技術的應用場景一般集中在哪些方面呢?除了修複緊急的 BUG,還有哪些真實場景下用過這個技術嗎?微信是如何評估是否需要通過打熱補丁的方式來處理一些問題的呢?

正如我之前的一篇文章來說,在 Android 熱補丁技術的應用比 iOS 更加容易。我們可以完全做到無感知的開發,推給用戶等。這裡面的應用場景有很多,用戶調試,版本升級,發佈需求,Abtest 等等。

Q6:想問下大神,對於替換 app 中使用的第三方 jar 包,有具體實踐嗎?

抱歉,這部分還沒有實踐。原理上是沒問題的,如果第三方的 jar 包是集成到源碼,那麼編譯新包的時候已經可以帶上改變。如果第三方的 jar 包是動態載入的,也是沒有問題的。我們通過 parent classloader 的方式,查找順序也會在你們之前。

Q7:patchCoreSDK 怎麼繞過 換 classloader 後跨 dex 載入類 accesserror 的問題?有對 patchcoreSDK 做強制訪問隔離嗎?

是的,Tinker 框架分為兩部分,核心載入代碼,成為 loader 類,這裡大概有十幾個類,他們是不允許修改的。其他大部分 Tinker 的類也是可以通過補丁修改的,這裡 Tinker 框架已經做了處理,即在新合成的 Dex,我們已經刪除了 loader 相關的類,從而徹底避免了這個問題。

Q8:patch 成功後怎麼及時重啟其他進程?

為了保證各個進程的唯一性,我們有一個版本管理文件用於記錄當前補丁的版本。它分為 old 與 new 兩個欄位。同時做了約定,只有 patch 進程可以修改 new 欄位,只有主進程可以修改 old 欄位,其他所有進程啟動時都只會載入 old 欄位的補丁版本。然後主要主進程可以發起版本升級,即把 new 欄位賦值給 old 欄位,這個時候主進程要殺掉其他所有的進程,以保證統一性。
而及時重啟其他進程的問題,主要是在我剛纔講的 result service。在結果回調中,我們如果發現補丁已經成功了,我們可以設置主進程在後臺或者鎖屏時自殺,以達到最快的應用。

Q9:完全使用新的資源包是怎麼理解?舊的資源包會被替換刪除嗎?

舊的資源包是安裝的 apk,我們是不會刪掉的。我們只是反射系統的一些介面,把它替換成新的資源包

Q10:超級補丁方案,有沒有想過不採用插樁的方式,而是去 hook 檢驗的方法,就能緩解性能的問題?

事實上,有些人實現 hook preverify 標誌來避免插樁。但是看過底層代碼,就知道是不可行的。我們要知道系統檢查那個標誌位的真正原因,即使 hook 了 preverify 標誌,在真正運行過程中,由於 quck 指令以及 vtable 的優化,依然運行時會出問題。這個問題告訴我們,做事情需要知其然也要知其所以然。

Q11:合成新的資源和 so 是怎麼載入的?

so 可以通過反射 classloader 的 lib path,但是我們並不建議這麼多,一來是相容性問題,二來在某些機器上,多 abi 的判斷並不准確。我們更希望通過封裝代碼來支持。對於資源,我們處理是跟 dex 差不多,啟動時即反射調用。

Q12:是否有動態下發第三方的 jar 包,如何調用第三方 jar 包的方法。反射?

Tinker 框架只會合成輸入 pattern 下的 dex,而且在啟動的時候把他們載入。如果調用的問題,使用者自己決定的。

Q13:差量下發更新,合成的時候是否會有性能問題?是否支持(圖片)資源的差量下發?

合成的話,我們對於記憶體、GC 以及耗時都有大量的優化。即使是微信這樣體量的 app,從外部監控來看,大部分用戶都能在60秒以內完成。

Q14:需要在補丁合成載入之後才進入程式(交由用戶操作)嗎?

合成與載入是分開兩個過程,我們的原則是除非合成已經徹底完成,不然其他進程是不會去載入的。即補丁不會去影相其他進程的載入性能

Q15:代碼完全開源嗎?

對的,所有代碼都會開源,從編譯到各個模塊。

Q16:xposed 框架的那些插件,是通過反射調用替換值?那一般有啥方式保證安全性?保證 app 數據的安全性?

它們只要是反射調用微信的某些類,達到某些功能的篡改。事實上,如果在 root 下,單純的保護是比較難的。

Q17:為什麼要在補丁成功的時候加結果回調是為了啟動程式麽,但是和您剛纔說的為了實時上報?

回調結果是為了給使用者一個回調,在這個回調裡面它可以做各種各樣的工作。例如我彈出升級完成的 dialog。我設置鎖屏或者程式進入後臺後自殺,這可以加快補丁的應用

Q18:既然能載入 so 和資源,Tinker 能用於插件化嗎?

Tinker 當前沒有做四大組件的代理,但是 Tinker 未來絕對是具備這個能力的

Q19:merge 失敗後的補救機制是怎樣的?可以回退麽?

merge 失敗,我們會收到回調,這個時候我們不會載入的。在預設實現裡面,我們會刪除這些臨時文件。

Q20:這套框架目前是多少個人在維護呢?

Tinker當前有3個人在開發維護

Q21:請問資源是編譯到 arsc 中還是反射載入二進位流?

你的問題我不太明白,資源我們採用的是全量替換,即完全使用新的資源包

Q22:在加入 Tinker 之後,對各平臺的加固適配如何?微信是否有加固?

微信沒有使用加固,但是加固應該是不影響的,只需要把介面改一下就可以了。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • Android中常用的5大佈局方式有以下幾種: Android中常用的5大佈局方式有以下幾種: Android中常用的5大佈局方式有以下幾種: 線性佈局(LinearLayout):按照垂直或者水平方向佈局的組件。 幀佈局(FrameLayout):組件從屏幕左上方佈局組件。 表格佈局(TableL ...
  • 該系列教程概述與目錄:http://www.cnblogs.com/chengyujia/p/5787111.html 一、自定義控制項簡介 在本項目中,無論是游戲主區域還是虛擬方向鍵都是通過自定義控制項來實現的,我們有必要先對自定義控制項有個簡單的瞭解。而且通過自定義控制項的學習能更好的理解系統自帶控制項的 ...
  • 什麼是Dagger2 Dagger是為Android和Java平臺提供的一個完全靜態的,在編譯時進行依賴註入的框架,原來是由Square公司維護,現在由Google維護。 我們知道Dagger是一個依賴註入的框架,那麼什麼是依賴註入呢? 我們在activity中有可能會用到很多很多的類,這些類要在a ...
  • 1.參考文獻 http://hi.baidu.com/accpzhangbo/blog/item/52aeffc683ee6ec238db4965.html 2.解析 查看java.lang.System的源代碼,我們可以找到System.exit(status)這個方法的說明,代碼如下: /** ...
  • 一般2D游戲使用SurfaceView足夠,所以不要認為什麼都要使用GLSurfaceView(openGL),而且 GLSurfaceView的弊端在於適配能力差,因為很多機型中是沒有GPU加速的。 ...
  • 1. 下列哪些語句關於記憶體回收的說明是正確的? (b) A、 程式員必須創建一個線程來釋放記憶體 B、 記憶體回收程式負責釋放無用記憶體 C、 記憶體回收程式允許程式員直接釋放記憶體 D、 記憶體回收程式可以在指定的時間釋放記憶體對象 2. 下麵異常是屬於Runtime Exception 的是(abcd)(多選 ...
  • Analyze提示:Value stored to "xxx"is never read 分析:即當前變數沒有被使用,在當前類中搜索該變數發現只是被賦值並沒有被使用。 解決:解除這個提示:刪除或者註視這行代碼OK; Analyze提示:the left operand of ** is a garb ...
  • 一個類可以繼承另一個類的方法,屬性和其他特性。當一個類繼承其他類時,繼承類叫子類,被繼承類叫超類(或父類)。在Swift中,繼承具有單繼承的特點,每個子類只有一個直接父類,繼承是區分類與其他類型的一個基本特征。 在Swift中,類可以調用和訪問父類的方法,屬性和下標腳本,並且可以重寫這些方法,屬性和 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...