熱修復原理學習(1)熱修復技術介紹

今天開始學習熱修復的原理知識,學習方向是阿里團隊編寫的《深入探索Android熱修復技術原理》,所以研究的熱修復框架是Sophix

之前對熱修復的知識做過了解,具體是這一篇:熱修復原理學習

1.熱修復的出現

日常開發中,我們可能遇到下面的情況:

  • 線上版本出現了嚴重的bug,如果纔剛剛發版,或者下個版本已經規定了具體的計劃發佈,這個時候爲了解決bug,需要fix+測試+在各個應用市場重新發布,這會消耗大量的人力物力,代價比較大
  • 版本升級率不高,並且需要很長時間來完成版本覆蓋,此前版本的Bug就會一直影響不升級版本的用戶
  • 有一個小而重要的功能,需要短時間內完成版本覆蓋,比如節日活動

而熱修復框架就應運而生,它可以解決上述這些問題,對線上版本進行修復。

它的熱更新流程是這樣的:
在這裏插入圖片描述
可見,熱修復的開發流程更加靈活,它有以下幾大優勢:

  • 無需重新發版
  • 用戶無感知修復Bug,無需下載新的應用,代價更小
  • 修復Bug成功率高,把損失降到最低

2.基本概念

App的安裝包是 .apk文件,它其實是 ZIP封裝的,我們通過右鍵 apk文件->打開方式->zip文件打開,可以看到一個apk文件的目錄大概是這樣的:
在這裏插入圖片描述
下面來介紹一下這些文件:

  • AndroidMnifest.xml
    這個比較熟悉,只是這個地方的文件是二進制的,而且它合併了所有子項目的AndroidManifest文件
  • META-INF
    簽名相關的文件,它可以校驗APK中所有文件的合法性,從而識別唯一的開發者,不讓其他人隨意反編譯我的APK文件
  • classes.dex
    是Apk解壓後文件中最重要的組成部分,它是Android項目所有Java代碼最終編譯後的形式。由於Android是基於ART的,所以運行的是對 .class格式進行壓縮合成的 .dex格式文件
  • resources.arsc
    該文件包含了所有資源的ID,以及他們具體的ID值和類型信息。 平常開發中的 R.xx類型的ID,實際上都是由 32位數字組成的資源ID,他們有各種不同的類型,要麼是字符串,要麼是數字,要麼是資源路徑。而如果是資源路徑,必然指代的是res文件中的資源文件。
    arsc文件只是ID信息,而實際資源內容(xml、圖片信息)都是放在res目錄下的。
    在程序運行中,會先從arsc文件中找到相應ID所對應的資源路徑,然後再訪問實際的res文件中路徑所在的資源。
  • assets
    也是資源,只是這些資源是不帶資源ID的原始文件,因此,可以直接指定路徑來訪問這些資源。與arsc文件中的資源ID是完全不同的。
  • lib
    lib下就是 JNI相關的so庫了,它有 armeabi、x86等庫,apk會根據所安裝Android設備的CPU的實際架構來選擇具體加載哪個so庫
  • res
    存放所有資源文件的目錄

瞭解了Android系統的APK文件格式後,我們再結合App的運行過程,看下如何從本質上實現熱修復功能。

(1)關於AndroidManifest的修復
關於AndroidManifest出現的bug是無法修復的。因爲他是由系統進行解析的,系統會直接獲取安裝包裏唯一的AndroidManifest文件,在解析過程中不會訪問補丁包的信息。
因此如果想要增加四大組件,通常來說不可以直接添加的。
因此我們需要在AndroidManifest裏面加載新增組件時,通常的做法是預先在安裝包的AndroidManifest埋入代理的組件,每次新增組件時,進行偷樑換柱,通過預埋的代理組件實現與系統進程間的通信,這也是 插件化Activity的做法。

(2)代碼的修復
由於所有的Java代碼最終都會編譯爲 classes.dex格式文件,因此任何的熱修復方案,想要改變代碼邏輯,都需要在補丁包裏包含一個新邏輯的 dex文件,然後在程序運行的時候加載這個dex文件,並且改變執行流,從執行原有安裝包裏的classes.dex文件引導到新的dex文件中去。

(3)資源的修復
主要修改資源包的內容。正常情況下,資源包就是整個APK安裝包,如果想要新增一個原有安裝包裏不存在的資源,就必須修改資源包的內容。
所以,就必須要想辦法把原有的安裝包替換爲新資源包,或者把新的資源包插入程序的查找過程中。而有些資源,比如桌面圖標、通知欄圖標以及RemoteView之類的資源,是由系統直接解析安裝包裏的資源得到的,因此對於這類資源, 任何熱修復方案都無法進行資源的替換和修復。

(4)so庫的修復
so庫的修復思路應該是最明確的。在Android系統中,所有so庫都是由 System load進行加載的,因此只要找到辦法在加載的時候優先加載補丁包的so庫,而不是原有安裝包的so庫,就能夠進行完整的低層代碼替換了。

3. Sophix介紹

阿里系的熱修復框架最初爲 Dexposed,這個框架方案由於對DVM過於依賴,導致在Android5.0後,該框架作廢。
後來阿里支付寶團隊又研發了Andfix,還有之後基於 Andfix、對業務邏輯解耦的阿里百川Hotfix。他們可以兼容 DVM/ART,而且可以滿足基本的代碼修復。但是他們也有缺點:

  • Andfix是由侷限性的,其低層固定結構的替換方案穩定性不是很好
  • 通過改造代碼繞過限制來達到相同的修復目的,但是這種方式既不美觀也不方便
  • 只提供了代碼層的修復,資源層和so庫層的修復還未能實現

業界除了阿里系的熱修復框架,還有微信的Tinker,餓了麼的 Amigo,美團的Robust等,不過他們各自都有自身的侷限性,或者不夠穩定,或者補丁過大,或者效率低下,或者使用起來過於煩瑣。實際上用戶體驗並不太好。

而淘寶團隊所研發的Sophix,在Android熱修復的三大領域:代碼、資源、so庫,以及安全性和易用性都做的很好。

下面看下 Sophix的方案與其他方案的對比(同阿里系最新比對 ):
在這裏插入圖片描述

Sophix的功能非常強大,除了不支持四大組件的增加(因爲會讓原有的代碼變的臃腫,對App運行流程的侵入性很強)。所以使用也是會收費,沒有開源,但設置了免費閾值。

Sophix的核心設計理念,就是非侵入式。
Sophix的打包過程不會侵入Apk的構建流程中,我們所需要的知識已經生成完畢的新舊APK,而至於APK是如何生成的則不需要關心,無論是AS打包出來的,還是Eclopse打包出來的,在生成補丁的過程中不會改變任何打包組件,也不插入任何AOP代碼。

在Sophix的方案中,唯一需要的就是初始化和請求補丁兩行代碼,甚至連Application入口類都不需要做任何更改,這樣就給開發者帶來了最大的透明度和自由度。

4.技術概覽

4.1 代碼修復

代碼修復有兩大主要方案,一種是阿里系的低層替換方案,另外一種是騰訊系的類加載方案。
這兩類方案各有優缺點:

  • 低層替換方案限制頗多,但時效性好,加載輕快,立即見效
  • 類加載方案時效性查,需要重新冷啓動才能見效,但修復範圍廣,限制少。

(1)低層替換方案
底層替換方案是在已經加載的類中直接替換原有的方法,是在原有類的基礎上進行修改的,因而無法實現對原有類進行方法和字段的增刪,因爲這樣將破壞原有類的結構。

一旦補丁類出現了方法個數的增加或者減少,就會導致這個類以及整個dex的方法個數的變化。方法個數的變化伴隨着方法索引的變化,這樣在訪問方法時就無法正常索引到正確的方法了。如果字段發生了增加或減少,就會和方法個數變化一樣,所有字段的索引都會發生變化
而且更嚴重的問題是,如果程序運行中某個類突然增加了一個字段,那麼對於已經產生的這個類的實例,他們還是原來的結構,這是無法改變的。而新方法使用到這些老的實例對象時,訪問新增字段就會產生不可預期的後果。

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

傳統的底層替換方案,不論是Dexposed、Andfix還是其他安全界的Hook方案,都是依賴直接修改虛擬機方法實體的具體字段實現的。例如,修改DVM的JNI函數指針、修改類或方法的訪問權限等,這樣就帶來一個很嚴重的問題,由於Android系統是開源的,各個手機廠商都可以對代碼進行改造,而Andfix裏 ArtMethod結構體的結構是根據公開的Android源碼中的結構固定編寫的。如果某個廠商對這個ArtMethod結構體的結構進行了修改美酒和原有開源代碼裏的結構不一致,那麼在這個修改過ArtMethod的設備上,通用性的替換機制就會出現問題。這便是不穩定的根源。詳情請看熱修復原理學習4.2節。

而阿里團隊也重新對代碼的底層替換方案原理進行了深入思考,從如何克服其固有限制和兼容性入手,以一種更加優雅的替換思路,實現了即時生效的代碼熱修復方案。
最終Sophix實現的是一種不修改底層具體結構的替換方式,這種不僅解決了兼容性問題,而且忽略了底層ArtMethod結構的差異,不需要兼容各個Android版本,代碼量大大減少。
即時以後Android版本不斷修改ArtMethod的結構,只要保證ArtMethod數結構體是以線性結構排列,就能直接使用與將來的版本。

(2)類加載方案
類加載方案的原理是App重新啓動後讓Classloader去加載新的類
因爲在App啓動到一半的時候,所需要發生變更的類已經被加載過了,在Android系統上是無法對一個類進行卸載的。如果不重啓,原有的類還在虛擬機中,就無法加載新的類。因此,只有下次重啓的時候,在還沒有運行到業務邏輯之前搶先加載補丁中的心累,這樣後續訪問這個類時,就會被解析爲新類,從而達到熱修復的目的。

來看看騰訊系三大類加載方案實現原理: QQ空間方案會侵入打包流程,並且爲hack添加一些無用的信息,實現起來很不優雅。
而Qfix方案,需要獲取底層虛擬機的函數,穩定性不夠,並且有個比較嚴重的問題----無法新增public函數

微信的Tinker是完整的全量dex文件加載,並且可以說是將補丁合成方案做到了極致,然而我們發現,Tinker的合成方案,是從dex的方法和指令維度進行全量合成,整個過程都是自己研發的,雖然可以很大程度節省空間,但是由於對dex內容的比較力度過細,實現較爲複雜,性能消耗比較嚴重。 實際上,dex的文件大小佔整個Apk的比例是比較低的,一個App裏面的dex文件並不是主要成分,而佔用空間大的主要還是資源文件,因此,Tinker方案的時間與空間轉換的性價比不高。

而Sophix,採用的也是全量合成dex的技術,該方案會直接利用Android原有的類查找與合成機制,快速合成新的全量dex文件。
這樣既不需要處理合成時方法數超過原有方法數的情況,也不會對dex的結構進行破壞性重構。
Sophix重新編排了包中dex文件的順序,這樣,在虛擬機查找類的時候,會優先找到 classes.dex中的類,然後纔是 classes2.dex…也可以看做是dex文件級別的類插樁方案。這個方式十分巧妙,它對舊包與補丁包中的classes.dex順序進行了打破與重組,最終使系統可以自然地識別到這個順序,以實現類覆蓋的目的。這將會大大減少合成補丁的開銷。

(3)雙劍合璧
Sophix的代碼修復體系正是同時涵蓋了這兩種方案。兩種方案的結合,可以實現優勢互補,完全兼顧的作用。

這兩種方案進行了重大的改進。在補丁生成階段,補丁工具會根據實際代碼的變動情況自動選擇,針對小修改,在底層替換方案限制範圍內的,就直接採用底層替換修復,這樣可以做到代碼修復即時生效。而對於代碼修改超出底層替換限制的,會使用類加載替換,這樣做雖然及時性沒那麼好,但是總歸可以達到熱修復的目的。

另外,運行時階段,Sophix還會再次判斷所運行的機型是否支持熱修復,這樣即使補丁支持熱修復,但由於機型底層虛擬機構造不支持熱修復,還是會走類加載方案,從而達到最好的兼容性。

4.2 資源修復

目前市場上的很多資源熱修復方案基本上都是參考了 Instant Run的實現。
實際上 Instant Run的推出正是推動這次熱修復浪潮的主要原因。

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

  1. 構造一個新的 AssetManager,並通過反射調用 addAssetPath(),然後把完整的新資源包加載到 AssetManager中。這樣就得到了一個含有所有新資源的 AssetManasger
  2. 找到所有值錢引用到原有的AssetManager的地方,通過反射,把引用處替換爲新的 AssetManager。

我們發現,其實大量代碼都是在處理兼容性問題和搜索到 AssetManager所有的引用處,真正的替換邏輯其實很簡單。

Sophix的做法:
Sophix並沒有直接參考 Instant Run技術,而是另闢蹊徑,構造了一個 package id爲 0x66的資源包,這個包裏面只包含需要改變的資源項,然後直接在原有的 AssetManager中通過 addAssetPath()函數添加這個包就行了。
由於補丁包的 package id爲0x66,不與目前已經加載的地址爲 0x7f的包衝突,因此直接加載到已有的 AssetManger中就可以直接使用了。
補丁包裏面的資源,只包含原有包裏面沒有的新增資源,以及原有的內容發生了改變的資源。並且 sophix採用更加優雅的替換方式,直接在原有的 AssetManager對象進行解析和重構,這樣所有原有對AssetManager對象的引用是沒有發生改變的,所以就不需要向Instant Run那樣進行繁瑣的修改了。

Sophix的資源修復方案在性能上超過了Google官方的 Instant Run方案,整個資源替換的方案優勢如下:

  • 不修改AssetManager的引用處,替換資源更快更完全(對比Instant Run以及所有的 模仿者的實現)
  • 不必下發完整包,補丁包中只包含變動的資源
  • 不需要在運行時合成完整包,不佔用運行時計算和內存資源

4.3 so庫修復

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

Sophix採用的是類似類修復反射注入方式,把補丁so庫的路徑插入到 nativeLibraryDirectories數組的最前面,就能夠達到加載so庫的時候是補丁so庫的目錄從而修復Bug的目的。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章