深入探索Android熱修復技術原理讀書筆記 —— 熱修復技術介紹

語言: CN / TW / HK

1.1 什麼是熱修復

對於廣大的移動開發者而言,發版更新是最為尋常不過的事了。然而,如果你 發現剛發出去的包有緊急的 BUG需要修復,那你就必須需要經過下面這樣的流程:

這就是傳統的更新流程,步驟十分繁瑣。總的來說, 傳統流程存在這幾大弊端:

  • 重新發布版本代價太大

  • 使用者下載安裝成本太高

  • BUG 修復不及時,使用者體驗太差

相應的,許多開發者找到了比較合適的解決辦法。

  1. Hybrid 方案。也就是把需要經常變更的業務邏輯以 H5  的方式獨立出來。而這種方案 , 需要傳統的 java  開發者學習前端語言,不僅增加了學習成本,而且還要對原先的邏輯 進行合適的抽象和轉換。並且,對於無法轉為  H5 形式的程式碼仍舊是無法修復的。

  2. 使用外掛化方案來解決問題,像 Atlas 或者 DroidPlugin  方案。 而這類方式,移植成本非常高,還要學習整套外掛化工具,對原先老程式碼的改 造。

於是,熱修復技術應運而生了。

1.2 技術沉澱

阿里系:

  • Dexposed:基於Xposed改進,針對Android Dalvik虛擬機器執行的Java Method Hook技術,但無法相容Android5.0以後的虛擬機器

  • Andfix:也是一種底層替換的方案,做到了 Dalvik 和 ART 的相容

  • Hotfix:結合實際工程中的使用Andfix的經驗,推出阿里百川Hotfix,但只提供了程式碼層面的修復,對於資源和so的修復還未實現

  • Sophix:2017年6月推出Sophix,打破了各家紛爭的局面,在程式碼修復,資源修復,so修復方面,都做到了業界領先

其他著名的熱修復,但是各自有各自的侷限性,補丁過大,效率低下,不夠穩定,用起來繁瑣:

  • 騰訊 QQ 空間的超級補丁

  • 微信的 Tinker

  • 餓了麼的 Amigo

  • 美團的 Robust

1.3 詳細比較

Sophix和Tinker與Amigo的比較:

各項指標都佔優,唯一不支援的就是四大元件的修復

1.4 技術概覽

1.4.1 設計理念

Sophix 的設計理念,就是非侵入性

  • 最終的實現只有兩個生成的新舊 apk,唯一要做的就是初始化和請求補丁兩行程式碼

  • 不會侵入 apk 的 build 流程中

  • 不改變任何打包元件

  • 不插入任何 AOP 程式碼

1.4.2 程式碼修復

程式碼修復有兩大主要方案,一種是阿里系的底層替換方案,另一種是騰訊系的類載入方案。

兩種方案各有優劣:

  • 底層替換方案限制頗多,但時效性最好,載入輕快,立即見效。

  • 類載入方案時效性差,需要重新冷啟動才能見效,但修復範圍廣,限制少。

底層替換方案

底層替換方案是在已經載入了的類中直接替換掉原有方法,是在原來類的基礎上 進行修改的。因而無法實現對與原有類進行方法和欄位的增減,因為這樣將破壞原有 類的結構。

一旦補丁類中出現了方法的增加和減少,就會導致這個類以及整個 Dex 的方法 數的變化。方法數的變化伴隨著方法索引的變化,這樣在訪問方法時就無法正常地索 引到正確的方法了。如果欄位發生了增加和減少,和方法變化的情況一樣,所有欄位 的索引都會發生變化。並且更嚴重的問題是,如果在程式執行中間某個類突然增加了 —個欄位,那麼對於原先已經產生的這個類的例項,它們還是原來的結構,這是無法改變的。而新方法使用到這些老的例項物件時,訪問新增欄位就會產生不可預期 的結果。

這是這類方案的固有限制,而底層替換方案最為人詬病的地方,在於底層替換的 不穩定性。

通過對程式碼的底層替換原理重新進行了深入思考,從克服其限制和相容性入 手,以一種更加優雅的替換思路,實現了即時生效的程式碼熱修復。

採用一種無視底層具體結構的替換方式,這種方式不僅解決了相容性 問題,並且由於忽略了底層 ArtMethod結構的差異,對於所有的Android 版本都不 再需要區分,程式碼量大大減少。即使以後的 Android版本不斷修改ArtMethod 成員,只要保證 ArtMethod 陣列仍是以線性結構排列,就能直接適用於將來的 Android 8.0、9.0 等新版本,無需再針對新的系統版本進行適配了。

類載入方案

類載入方案的原理是在 app 重新啟動後讓 Classloader 去載入新的類。因為在 app執行到一半的時候,所有需要發生變更的類已經被載入過了,在 Android 上 無法對一個類進行解除安裝的。如果不重啟,原來的類還在虛擬機器中,就無法載入新類。 因此,只有在下次重啟的時候,在還沒走到業務邏輯之前搶先載入補丁中的新類,這 樣後續訪問這個類時,就會 Resolve為新類。從而達到熱修復的目的。

再來看看騰訊系三大類載入方案的實現原理。

  1. QQ  空間方案會侵入打包流程,並 且為了 hack 新增一些無用的資訊,實現起來很不優雅。

  2. QFix  的方案,需要獲取 底層虛擬機器的函式,不夠穩定可靠,並且有個比較大的問題是無法新增 public函式。

  3. 微信的 Tinker 方案是完整的全量 dex  載入,並且可謂是將補丁合成做到了極 致。 Tinker 的合成方案,是從 dex 的方法和指令維度進行全量合成,整個過程都是自己研發的。雖然可以很大地節省空 間,但由於對 dex 內容的比較粒度過細,實現較為複雜,效能消耗比較嚴重。實際 上, dex 的大小佔整個apk的比例是比較低的,一個 app 裡面的dex 檔案大小並不 是主要部分,而佔空間大的主要還是資原始檔。因此, Tinker  方案的時空代價轉換的 價效比不高。

dex 比較的最佳粒度,應該是在類的維度。它既不像方法和指令維度那樣 的細微,也不像  bsbiff  比較那般的粗糙。在類的維度,可以達到時間和空間平衡的最 佳效果。基於這個準則,另闢蹊徑,實現了一種完全不同的全量 dex替換方案。

  • 直接利用  Android 原先的類查詢和合成機制,快速合成新的全量 dex。 麼一來,既不需要處理合成時方法數超過的情況,對於  dex  的結構也不用進行破 壞性重構。

  • 重新編排了包中dex 的順序。虛擬機器查詢類的時候,會優先找到 classes.dex 中的類,然後才是 classes2.dex、classes3.dex, 也可以看做是 dex 檔案級別的類插樁方案。這個方式對舊包與補丁包中 classes.dex  的順 序進行了打破與重組,最終使得系統可以自然地識別到這個順序,以實現類覆蓋的目 的。大大減少合成補丁的開銷。

雙劍合璧

既然底層替換方案和類載入方案各有其優點,把他們聯合起來不是最好的選擇 嗎?

Sophix 的程式碼修復體系正是同時涵蓋了這兩種方案。兩種方案的結合,可以實 現優勢互補,完全兼顧的作用,可以靈活地根據實際情況自動切換。

在補丁生成階段,補丁工具會根據實際代 碼變動情況進行自動選擇,

  • 針對小修改,在底層替換方案限制範圍內的,就直接採 用底層替換修復嗎,這樣可以做到程式碼修復即時生效。

  • 對於程式碼修改超出底層替 換限制的,會使用類載入替換,這樣雖然及時性沒那麼好,但總歸可以達到熱修復 的目的。

  • 執行時階段,Sophix  還會再判斷所執行的機型是否支援熱修復,這樣即 使補丁支援熱修復,但由於機型底層虛擬機器構造不支援,還是會走類載入修復,從而 達到最好的相容性。

1.4.3 資源修復

目前市面上的很多資源熱修復方案基本上都是參考了 Instant Run  的實現。實際 上, Instant Run  的推出正是推動這次熱修復浪潮的主因,各家熱修復方案,在程式碼、 資源等方面的實現,很大程度上地參考了 Instant Run 的程式碼,而資源修復方案正是 被拿來用到最多的地方。

簡要說來,Instant Run 中的資源熱修復分為兩步 :

  1. 構造一個新的AssetManager,並通過反射呼叫 addAssetPath, 把這個完 整的新資源包加入到  AssetManager  中。這樣就得到了一個含有所有新資源 AssetManager。

  2. 找到所有之前引用到原有AssetManager 的地方,通過反射,把引用處替換 AssetManager 。

新的實現方式:構造了一個 package id 為 0x66  的資源包,這個包裡只包含改變了的資源項,然後直接在原 有  AssetManager 中 addAssetPath 這個包就可以了。由於補丁包的 package id 為 0x66,不與目前已經載入的 0x7f 衝突,因此直接加入到已有的 AssetManager 中就可以直接使用了。

補丁包裡面的資源,只包含原有包裡面沒有而新的包裡面有 的新增資源,以及原有內容發生了改變的資源。並且,我們採用了更加優雅的替 換方式,直接在原有的  AssetManager  物件上進行析構和重構,這樣所有原先對 AssetManager物件的引用是沒有發生改變的,所以就不需要像Instant Run  那樣 進行繁瑣的修改了。

可以說,我們的資源修復方案,優越性超過了 Google官方的Instant Run 案。整個資源替換的方案優勢在於:

  • 不修改 AssetManager 的引用處,替換更快更完全。(對比 Instanat Run  及所有  copycat 的實現)

  • 不必下發完整包,補丁包中只包含有變動的資源。(對比 Instanat Run s Amigo 等方式的實現)

  • 不需要在執行時合成完整包。不佔用執行時計算和記憶體資源。(對比 Tinker  實現)

1.4.4 SO庫修復

SO 庫的修復本質上是對 native 方法的修復和替換。

我們採用的是類似類修復反射注入方式。把補丁 so 庫的路徑插入到 nativeLi- braryDirectories 陣列的最前面,就能夠達到載入 so  庫的時候是補丁 so  庫,而不 是原來  so 庫的目錄,從而達到修復的目的。

採用這種方案,完全由 Sophix 在啟動期間反射注入 patch 中的 so 庫。對開發 者依然是透明的。不用像某些其他方案需要手動替換系統的  System.load  來實現替 換目的。

1.5 本章小結

本章介紹了熱修復技術的主要使用場景和為業界帶來的變化。詳細說明了阿里巴 巴推出的熱修復解決方案  Sophix  的由來,同時與其他各大主流方案進行了比較。另 外,粗略介紹了熱修復所涉及的各個方面,並引導概述後續各個章節。