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

huansky發表於2021-05-06

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 Runs Amigo 等方式的實現)

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

1.4.4 SO庫修復

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

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

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

1.5 本章小結

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

  

參考文章

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

深入探索Android熱修復技術原理[book]

 

相關文章