干貨滿滿电抚,Android熱修復方案介紹(轉載)

原文鏈接:https://developer.aliyun.com/article/231111

場景研讀 2017-11-03 10747瀏覽量

簡介: 在云棲社區(qū)技術直播中性芬,阿里云客戶端工程師李亞洲(畢言)從技術原理層面解析和比較了業(yè)界幾大熱修復方案偏窝,揭開了Qxxx方案钢坦、Instant Run以及阿里Sophix等熱修復方案的神秘面紗兔港,幫助大家更加深刻地理解了代碼插樁、全量dex替換范舀、資源修復等常見場景解決方案,本文干貨滿滿了罪,精彩不容錯過锭环。

摘要:在云棲社區(qū)技術直播中,阿里云客戶端工程師李亞洲(畢言)從技術原理層面解析和比較了業(yè)界幾大熱修復方案泊藕,揭開了Qxxx方案辅辩、Instant Run以及阿里Sophix等熱修復方案的神秘面紗,幫助大家更加深刻地理解了代碼插樁娃圆、全量dex替換玫锋、資源修復等常見場景解決方案,本文干貨滿滿讼呢,精彩不容錯過撩鹿。

以下內容根據(jù)演講視頻以及PPT整理而成。

視頻分享鏈接悦屏,點擊這里节沦!

在傳統(tǒng)的修復模式下键思,如果線上的App出現(xiàn)Bug之后進行修復所需要的時間成本非常高,這是因為往往需要發(fā)布一個新的版本甫贯,然后將其發(fā)布到對應的應用商城中吼鳞,然后通知用戶下載和更新自己的App。但是尤其在Android這樣沒有統(tǒng)一應用市場的環(huán)境下叫搁,修復周期可能需要以周來計數(shù)赔桌。而隨著App的業(yè)務越來復雜、代碼量越來越大渴逻,出現(xiàn)Bug的概率也會越來越高纬乍,如果繼續(xù)按照傳統(tǒng)的修復模式就很難滿足業(yè)務發(fā)展的需求。正是因為這樣的現(xiàn)狀裸卫,很多Android端開發(fā)的同學就開始思考是否有一種在不發(fā)版的前提下修復線上Bug的技術仿贬,也就是所謂的熱修復技術。通過這幾年的發(fā)展墓贿,熱修復技術也得到了很大的發(fā)展茧泪,很多公司也具有了比較成熟的熱修復方案,同時這些熱修復方案也大規(guī)模地在生產環(huán)境中進行了實踐聋袋。本次會選取幾個比較具有代表性的熱修復方案與大家分享队伟。

在本次的分享中主要會講到三種技術方案:Qxxx(化名)方案、Instant Run和Sophix幽勒。Qxxx屬于比較早期的修復方案嗜侮,而現(xiàn)在由于種種原因可能不會成為大家在進行熱修復時的首選方案,但是其技術原理給了我們很大啟發(fā)啥容,所以在本次的技術分享中將Qxxx方案放在第一個進行介紹锈颗。第二種方案叫做Instant Run,這是所有從事Android開發(fā)的同學都比較熟悉的一種方案咪惠,嚴格意義上來講Instant Run其實不算是一個熱修復的方案击吱,它僅僅是作為Android Studio來提高開發(fā)效率的一個功能而已,但是其背后的技術原理卻和很多修復方案具有很多相通的地方遥昧。第三種方案叫做Sophix覆醇,它是阿里巴巴剛剛發(fā)布的一種修復方案,它代表著新一代的熱修復方案炭臭,Sophix的功能更加強大而且能夠覆蓋的場景也非常廣永脓,所以也是一個比較優(yōu)秀的方案。

一鞋仍、Qxxx方案解析

1.1 Qxxx方案原理介紹

[圖片上傳失敗...(image-ccc58d-1596530601128)]

可能大家對于Qxxx方案比較了解常摧,它也是來自于業(yè)界一家非常優(yōu)秀的公司所提供的方案。首先分享Qxxx方案比較基礎的特性凿试,它是基于Android dex分包方案排宰,其次它最關鍵的技術點在于利用字節(jié)碼插樁的方式繞開了預校驗問題似芝,這也是Qxxx方案最為核心的一點。Qxxx只支持App重啟之后才能修復板甘,也就是App在運行的時候加載到了補丁包也不能及時修復党瓮,需要App重新啟動的時候才會修復,這是因為Qxxx方案是基于類加載區(qū)需要重新加載補丁類才能實現(xiàn)的盐类,所以必須進行重啟才能修復寞奸。此外,Qxxx方案只支持到類結構本身代碼層面的修復在跳,不支持資源的修復枪萄。

1.2 Android端的類加載原理
接下來為大家簡單介紹Android端類的加載原理。其實Android的類加載和Java的類加載比較類似猫妙,都是通過ClassLoader類加載器進行加載瓷翻,唯一的區(qū)別就是Android類加載器加載的是dex文件,所以在Android中加載器的基類叫做BaseDexClassLoader割坠,這個類之下會有兩個子類齐帚,一個叫做PathClassLoader,它負責加載Android的SDK彼哼,當代碼中引用到Android框架的本身類的時候都是通過PathClassLoader進行加載的对妄;而另外一個子類叫做DexClassLoader,這個類就是用于加載業(yè)務層面代碼的加載器敢朱。

[圖片上傳失敗...(image-6daa86-1596530601128)]

早期在Android端只能加載一個dex文件剪菱,后來隨著代碼越來越多,一個dex文件已經無法存放所有代碼了拴签,所以需要加載多個dex文件铺董,這無論是在什么虛擬機內都可以通過相同的手法解決侵俗。而解決的關鍵就是無論有幾個dex文件费彼,所有的dex文件最后最后被加入到內存中都會形成一個數(shù)組叫做dexElement削咆。而DexClassLoader的原理就是當系統(tǒng)需要加載某一個類的時候,DexClassLoader就會去遍歷dexElement數(shù)組杖剪,當它遍歷到某一個元素的時候,在這個元素所對應的文件找到目標類的時候就會停止遍歷并及時返回驰贷。所以當在不同的dex文件里面出現(xiàn)兩個一樣的類的時候盛嘿,哪一個被先遍歷到哪個就會被先加載。正是基于這樣的原理括袒,Qxxx方案的研發(fā)同學就想到如果編寫的代碼存在Bug次兆,就可以發(fā)布一個補丁包,而這個補丁包也是dex文件锹锰,并且使這個dex文件能夠首先被DexClassLoader加載到芥炭,就能夠達到修復的效果漓库。所以就是需要通過一定的手段將補丁包的dex文件放在dexElement數(shù)組中的第一位,那么就可以首先加載補丁中的類园蝠,進而達到修復的效果渺蒿。

1.3 類預校驗問題
而實際情況中,做這樣的方案是不行的彪薛。直接進行替換就會出現(xiàn)預校驗的問題茂装。

[圖片上傳失敗...(image-8c19d-1596530601128)]

而預校驗問題出現(xiàn)的原因在于當App被安裝到設備上之后,會存在一個叫做dex opt的過程善延,也就是將dex文件進行優(yōu)化少态,在優(yōu)化之后dex文件將會變成odex文件,而在優(yōu)化過程中就會有一個預校驗的問題易遣。當某一個類所有的構造方法彼妻、私有方法以及重載方法所引用的其他類和這個類本身都來自于同一個dex文件的時候,這個類就會被打上class_ispreverified標簽豆茫。所以如果加載的類來自于補丁文件侨歉,而補丁文件和之前的文件必然不屬于同一個dex,而本身的那個類已經被打上了class_ispreverified標簽澜薄,但是在運行時又引用了其他dex的類为肮,這樣就必然會出現(xiàn)錯誤。所以Qxxx方案所需要解決的關鍵問題就是預校驗肤京。

[圖片上傳失敗...(image-64384b-1596530601128)]

1.4 字節(jié)碼注入
針對預校驗問題颊艳,有同學可能會認為只需要在寫代碼的時候引用一些類就可以了,但是這在實際情況下卻是非常困難的忘分,因為本身Android在打包代碼的時候就會盡可能地將相互依賴的類打包在同一個dex里面棋枕,所以依靠打包方案本身是很難解決這個問題的。但是預校驗問題也不是沒有辦法解決的妒峦,解決的思路是當這些類已經被編譯完成之后重斑,在字節(jié)碼的層面去注入一些來自于其他dex的類。

[圖片上傳失敗...(image-c3032b-1596530601128)]

幸運的是Android的gradle插件也提供了這樣的一些接口肯骇,叫做Transform的API窥浪。這個API會提供一個調用的時機,當代碼文件被編譯成JAR但是還沒有被打成dex的時候笛丙,提供了在這個時期做一些事情的接口漾脂。正是利用這個接口,當拿到編譯完成的字節(jié)碼文件之后胚鸯,可以對其進行字節(jié)碼的注入骨稿,進行所謂的插樁,插入一些來自于其他dex文件的類,這樣當App再被安裝并執(zhí)行dex opt過程的時候就不會再被打上預校驗的標簽坦冠,同時能夠成功加載補丁了形耗,這樣的方案也是非常巧妙并且有效的。

1.5 代碼插樁
但是為什么慢慢地大家開始覺得Qxxx方案并不好呢辙浑?其原因就在于插樁并不是一個非常好的方式激涤,它所帶來的開銷是非常大的。在dex opt的過程中會執(zhí)行一個驗證的過程例衍,再執(zhí)行一個優(yōu)化的過程昔期,最后將dex文件轉成odex文件。因為進行了插樁佛玄,所有的類都沒有被打上預校驗的標簽硼一,所以驗證和優(yōu)化這兩個過程會被放在真正類加載的時候去執(zhí)行,如果一兩個類在運行的時候進行加載和優(yōu)化對于App的性能的影響不大梦抢,但是現(xiàn)在的App越來越復雜般贼,當有成千上萬的類需要在運行時進行加載和優(yōu)化的時候,所帶來的開銷就是非嘲路裕可觀的了哼蛆。

[圖片上傳失敗...(image-a95e1-1596530601128)]

曾經有某公司的同學進行過測試,在插樁和不插樁的情況下去比較加載700個類和啟動App的情況霞赫。實驗結論是:在插樁的情況下腮介,700個類的加載時間需要600多毫秒,而在不插樁的情況下只需要80多毫秒端衰,兩者相差了近8倍叠洗;在啟動App的層面,插樁也會明顯比不插樁的情況慢了很多旅东。所以可以說插樁所帶來的性能開銷是非常大的灭抑,甚至可以說使用這種方法進行熱修復是一種得不償失的選擇,雖然實現(xiàn)了熱修復的方案抵代,但是因此丟失了程序良好的性能腾节,也正是因為這個原因,Qxxx方案逐漸被生產環(huán)境拋棄了荤牍。

二案腺、Instant Run方案解析

從嚴格意義上來講,Instant Run其實并不算一個熱修復方案康吵,它只是一個優(yōu)化開發(fā)效率的機制救湖。在傳統(tǒng)的開發(fā)模式中,當在開發(fā)的過程中對代碼進行了一些改動就會進行全量的構建涎才,然后將一個完整的App部署到測試機上,之后進行應用重啟,然后就可以看到代碼的變化與運行效果的變化耍铜。

[圖片上傳失敗...(image-70055e-1596530601128)]

而隨著App越來越復雜邑闺,全量構建的過程本身會變得非常耗時,尤其是在需要頻繁地進行代碼改動觀察效果的時候棕兼,這就會嚴重地影響開發(fā)效率陡舅。基于上述的現(xiàn)狀伴挚,Android Studio在2.0版本的時候就發(fā)布了Instant Run新特性靶衍。Instant Run新特性的原理就是當進行代碼改動之后,會進行增量構建茎芋,也就是僅僅構建這部分改變的代碼颅眶,并將這部分代碼以補丁的形式增量地部署到設備上,然后進行代碼的熱替換田弥,從而觀察到代碼替換所帶來的效果涛酗。其實從某種意義上講,Instant Run和熱修復在本質上是一樣的偷厦。

2.1 Instant Run打包邏輯

[圖片上傳失敗...(image-ce6bb1-1596530601128)]

在接入Instant Run之后商叹,與傳統(tǒng)方式相比,在進行打包的時候會存在以下四個不同點:

  1. manifest注入只泼;大家都知道一個Android工程的所有組件都會注冊到manifest文件下剖笙,在這部分中,Instant Run會生成一個自己的application请唱,然后將這個application注冊到manifest配置文件里面去弥咪,也就是說當整個App運行起來的時候,首先執(zhí)行的就是application這個類籍滴,也就是運行的是Instant Run本身的框架酪夷,它可以去做一系列準備工作,當這些工作完成之后再去運行業(yè)務代碼孽惰。
  2. Instant Run代碼放入主dex晚岭;manifest注入之后,會將Instant Run的代碼放入到Android虛擬機第一個加載的dex文件中勋功,包括classes.dex和classes2.dex坦报,這兩個dex文件存放的都是Instant Run本身框架的代碼,而沒有任何業(yè)務層的代碼狂鞋。正是因為以上的原因片择,當整個App運行起來的時候首先執(zhí)行的都是Instant Run的代碼。
  3. 工程代碼插樁——IncretmentalChange骚揍;這個插裝里面會涉及到具體的IncretmentalChange類字管。
  4. 工程代碼放入instantrun.zip啰挪;這里的邏輯是當整個App運行起來之后才回去解壓這個包里面的具體工程代碼,運行整個業(yè)務邏輯嘲叔。

[圖片上傳失敗...(image-9aeb9b-1596530601128)]

在App剛開始啟動的時候亡呵,Instant Run會做以下三件事情:

  1. 當bootstrap application啟動之后會首先加載classes.dex和classes2.dex這兩個主dex文件,當這兩個主dex文件啟動之后硫戈,就會啟動AppServer服務锰什。這里可以將AppServer理解為一個服務器,它會與IDE也就是Android Studio建立連接丁逝。當連接建立之后汁胆,后續(xù)在開發(fā)的過程中的代碼改動所形成的補丁包都會通過這個連接下發(fā)到App上,并且通過AppServer接收霜幼,再通過相應的處理使得補丁生效嫩码。
  2. 當完成了第一個步驟之后,會用本身的ClassLoader去加載instantrun.zip包里面真正的工程代碼辛掠。
  3. 最后一步谢谦,將宿主application替換成真實的realApplication,然后真正地運行自定義application里面的邏輯萝衩,達到隱藏自身的效果回挽。

2.2 Instant Run熱插拔、溫插拔和冷插拔簡介
當App啟動之后會啟動一個AppServer服務器的連接猩谊,當它加載到patch之后會去判斷patch是否能夠進行熱插拔千劈、溫插拔和冷插拔,然后再去做各種方式所對應的事情牌捷。

[圖片上傳失敗...(image-9f3bf1-1596530601128)]

這里簡單介紹一下在Instant Run里熱插拔墙牌、溫插拔和冷插拔這三個修復方式的概念:

  • HotSwap(熱插拔):修改方法實現(xiàn)后代碼可以實時生效,不需要重啟App也不需要重啟activity暗甥,只要加載補丁之后就可以馬上生效喜滨。通常情況下,熱插拔只適用于方法體內部的邏輯改變撤防。
  • WarmSwap(溫插拔):主要針對于需要修改或刪除資源的情況虽风。溫插拔不需要重啟App,但是需要重啟當前的activity后才能生效寄月。
  • ColdSwap(冷插拔):主要針對于改變了類的結構辜膝、繼承關系、實現(xiàn)接口等情況漾肮,此時因為類結構本身被改變了厂抖,需要重新去加載這個類,所以需要重啟App之后才能生效克懊。

2.3 Instant Run熱插拔(HotSwap)原理解析
首先IDE下發(fā)patch忱辅,加載到補丁之后七蜘,在App層Instant Run的框架會通過AppPatchLoader去找到哪些類需要被修復,當找到需要被修復的類之后再通過反射的手段將類中的$change變量設置為已經修復后的類耕蝉。這樣當執(zhí)行MainActivity的onClick方法的時候實際上執(zhí)行到的是MainActivity&override的onClick方法崔梗,從而實現(xiàn)了熱修復。

[圖片上傳失敗...(image-c4406f-1596530601128)]

2.4 Instant Run溫插拔(WarmSwap)原理解析
對于溫插拔而言垒在,需要首先簡單介紹一下資源修復的邏輯。其實對于Android框架比較熟悉的同學都清楚扔亥,在每一個activity里面都會有一個叫做mResource的變量场躯,這個mResource變量指向一個Resource對象。在Resource對象中會存在一個指向AssetManager的mAsset變量旅挤,而AssetManager類才是真正去管理和維護所有對于資源的訪問的具體類踢关。AssetManager類里面會有兩個具體成員,一個是framework-res.apk粘茄,其是系統(tǒng)自帶的資源签舞,另一個則是App本身的資源,而所有對于資源的訪問最終都會走到AssetManager類中柒瓣。正是因為這樣的機制儒搭,Instant Run就是通過替換AssetManager的方式達到資源修復的效果。

[圖片上傳失敗...(image-af0940-1596530601127)]

具體來說芙贫,當資源發(fā)生改變需要進行修復的時候搂鲫,IDE會發(fā)布一個資源的補丁發(fā)到終端之上,之后Instant Run會新建一個AssetManager磺平。新建的AssetManager里面AppResource的指針就會指向資源的補丁魂仍,當指向這個之后再去遍歷所有的activity,將其mAssert指針指向新建的AssertManager拣挪。這樣之后所有的activity指向的都是重現(xiàn)建立的AssertManager擦酌,此時只需要去重啟activity,那么所有的資源訪問就會來自新的資源包里面的資源菠劝,這樣也就達到了資源修復赊舶,也就是溫插拔的效果。

[圖片上傳失敗...(image-fd0d2c-1596530601127)]

需要注意的是溫插拔方案只適用于開發(fā)階段闸英,這是因為這種方案在補丁下發(fā)的時候會下發(fā)一個完整的全量資源包锯岖,如果將這種方案應用于線上就會產生比較大的開銷。因為整個App里面大部分都屬于資源甫何,如果因為僅僅修復其中的一兩個資源就去下發(fā)一個完整的資源包出吹,就會造成較大的開銷。綜上所述辙喂,溫插拔方案并不適用于整體線上環(huán)境捶牢,而只是一個開發(fā)階段的優(yōu)化手段鸠珠。

2.5 Instant Run冷插拔(ColdSwap)原理解析
之前提到所有的用戶代碼都被寫到instantrun.zip包里面,當代碼結構本身發(fā)生了變化之后秋麸,可以在把對應的代碼補丁下發(fā)到App之上的時候渐排,將對應的patch寫入對應的Instant Run的路徑底下,再重新進行dex opt的過程灸蟆,之后框架就可以加載對應的類了驯耻。

[圖片上傳失敗...(image-1c1b19-1596530601127)]

instantrun.zip里面的類都是在運行起來之后才去加載的,這部分的加載是可控的炒考,所以可以進行簡單的dex文件替換可缚,之后再去做修復。之所以dex文件會被切成很多片斋枢,是因為如果只修改了某個類帘靡,只發(fā)單一的class補丁放到里面同樣會遇到之前所提到的預校驗的問題,所以必須要去進行一次dex opt的過程瓤帚,才可以繞開預校驗的問題描姚。

2.6 Instant Run方案總結

[圖片上傳失敗...(image-f42361-1596530601127)]

  • 首先,熱插拔的優(yōu)勢在于其不需要重啟戈次,只需要代碼補丁被下發(fā)到端上之后就可以實時地看到修復效果轩勘。但是熱插拔的劣勢也很明顯,因為使用了插樁朝扼,所以其性能的開銷會非常大赃阀。
  • 其次,對于溫插拔而言擎颖,它的優(yōu)勢是可以實現(xiàn)資源修復榛斯,但是其劣勢就是這種方案會下發(fā)全量資源包,開銷也是非常大的搂捧。
  • 最后驮俗,對于冷插拔而言,它支持完整類的替換允跑,但是也存在分包的限制王凑,必須要去做切片,當修改了某一個類之后需要把這個類所有所屬的dex類都打成一個新的dex然后下發(fā)到端才可以聋丝。

三索烹、Sophix方案解析

Sophix是阿里巴巴剛剛推出的一款無侵入的熱修復方案,本次分享中就為大家揭曉Sophix的神秘面紗弱睦,看看它到底是怎樣實現(xiàn)的百姓。
3.1 Sophix及時修復(Andfix)原理解析
Sophix也是支持及時修復的,在這一點上與Instant Run一樣况木,對于方法體邏輯的修改可以在App不重啟的情況下進行垒拢。Sophix的及時修復方案其實早在阿里曾經開源的Andfix方案里面就已經實現(xiàn)了旬迹。

大家都知道所有的類被加載之后,其方法都會被放在方法區(qū)求类,這是Java層面的概念奔垦,其實這些方法區(qū)在native層也就是C層面都會有各自對應的結構體來描述對應的方法以及執(zhí)行的邏輯。如果某一個類的方法出現(xiàn)了Bug尸疆,那么可以去新建一個類椿猎,把修復后的方法放到這個類里面,同時把原來那個類的方法的指針指向新方法的方法體就可以實現(xiàn)方法體的替換仓技,從而實現(xiàn)熱修復的效果鸵贬。

[圖片上傳失敗...(image-1f991-1596530601127)]

Andfix方案很早就已經開源了,大家如果感興趣可以去GitHub上拿到源碼進行更進一步的分析脖捻。總結而言兆衅,就是會通過一個工具對于新的和老的兩個apk進行diff操作地沮,當diff完成之后就可以找到某一個有被修改了的方法的類,當修改之后就會生成一個新的patch的dex文件羡亩,這個文件里面就會有被修改的方法摩疑。首先這個類可能會被改名,但是里面會存在一個同名方法畏铆,這個方法的注解里面會標明其所替換的方法以及被替換方法所屬的類雷袋,當這個patch被推送到App端上之后,對應的Andfix的patchManager就會去加載補丁包辞居,首先進行校驗楷怒,之后加載補丁里面的類,并通過類中的注解去找到它所要替換的類瓦灶,當兩個類都找到之后再將對應的方法找出來鸠删,再在native層面對于具體方法的邏輯指針進行替換,從而使得指向原有方法的指針指向新的方法贼陶,從而達到及時修復的效果刃泡。

雖然Andfix及時修復方案看上去很美好,也很漂亮碉怔,既能夠實現(xiàn)及時修復又沒有使用插樁付出性能上的代價烘贴,但是這個方案也存在很大的限制。之前提到了任何虛擬機在native層都會有對應的結構體來描述方法撮胧,而在不同的虛擬機上桨踪,描述方法的結構體都是不一樣的,所以需要針對不同的Android虛擬機版本去做不同的適配來匹配不同的結構體趴樱,這樣一來兼容性的操作就會非常多馒闷。大家都很清楚Android端各個廠商都會定義自己的虛擬機酪捡,這時候就無法知道方法所對應的結構體內部是什么樣的,也就無法實現(xiàn)方法的替換了纳账,所以Andfix方案在現(xiàn)實的環(huán)境中存在很大的限制逛薇,兼容性會受到非常大的挑戰(zhàn)。

[圖片上傳失敗...(image-4ab268-1596530601127)]

而在Sophix里面卻非常巧妙地解決了上述問題疏虫。Sophix方案中提出了一個思想就是不需要去關心方法的結構體內部具體是什么樣的永罚,只需要進行一次整體的替換就可以了。原來需要一個成員一個成員地進行遍歷替換卧秘,現(xiàn)在是整體替換呢袱,但是本質是沒有區(qū)別的。同時也不關心結構體內部要做哪些操作以及每個成員變量所代表的含義翅敌,只需要進行整體替換就可以了羞福。這樣做有兩個好處,一個是比較簡單蚯涮,另一個就是不需要了解虛擬機每個方法體的內部結構治专,所以理論上對于所有的虛擬機版本都是適用的。

但是遭顶,雖然可以使用整體復制的方式去做一次性的結構體替換张峰,但是前提是必須要知道方法結構體的尺寸大小,只有在知道這些之后才能進行替換棒旗,這也就是第二個難題喘批,因為不同的虛擬機版本的結構體大小也不同,那么如何去知道結構體大小呢铣揉?對于這一點Sophix方案的解決方法也非常巧妙饶深。

[圖片上傳失敗...(image-a0b490-1596530601127)]

大家都知道當一個類被加載的時候,其方法都會被放在方法區(qū)老速,而且同一個類的方法會被緊密地排列在一起粥喜,而我們可以拿到兩個方法的地址起始值,而這兩者之間的差值就是第一個方法結構體的大小橘券。正是基于這一點额湘,只要去構造一個只有兩個靜態(tài)方法的類,那么就可以通過獲取這兩個方法的起始地址相減拿到方法對應結構體的大小旁舰,從而進行整體的替換锋华。

總之,Sophix及時修復的方法是非常巧妙的箭窜,既沒有用到插樁毯焕,同時又不需要考慮兼容性,在性能層面和兼容性層面都具有很好的保障。從及時修復的角度來看纳猫,Sophix的確有“四兩撥千斤”的功效婆咸。

3.2 Sophix冷啟動修復原理解析
上面提到的及時修復只能針對方法體內部結構被修改的場景,而對于類本身結構的改變芜辕,及時修復就沒有辦法了尚骄,這時候就需要用到冷啟動修復。冷啟動修復就是需要下發(fā)一個新的補丁侵续,在補丁中會有一個新的補丁類倔丈,在App重啟的時候會優(yōu)先加載這個補丁類達到去替換原有Bug類的效果。

對于冷啟動修復而言状蜗,針對于不同的虛擬機有不同的原則需五,Android主流的Dalvik和ART兩個虛擬機,它們最大的區(qū)別就是是否支持多個dex文件的加載轧坎。ART也就是Android 5.0以上的虛擬機本身就支持多個dex文件加載宏邮,而Dalvik卻不支持多個dex加載,只支持一個dex加載缸血,如果需要支持多個dex加載則需要引入multi-dex方案蜀铲。而Dalvik和ART加載多個dex文件的不同卻決定了它們需要采用不同熱修復方案的原因。

[圖片上傳失敗...(image-a055d2-1596530601127)]

ART本身支持多個dex加載属百,所以在程序啟動之前ART已經把多個dex文件都加載進來了,在運行后所有的類都已經被放在ClassLoader里面了变姨,而不需要再去做加載工作族扰。而Dalvik使用的multi-dex方案實際上在程序運行之前只加載了classes.dex文件,而剩下的其他的dex文件都是在程序啟動之后application運行的時候再去加載的定欧,所以可以看作在程序運行時進行dex加載渔呵,所以兩者之間存在著本質的區(qū)別。

在做冷啟動修復的時候砍鸠,Sophix的根本原則就是非侵入式扩氢,不能對于App本身有任何改造,同時也要保證整個App的性能爷辱,所以不能使用插樁的方案录豺,也必須要做到dex的全量替換,重新去執(zhí)行dex opt過程生成新的odex饭弓,再去把dexElement數(shù)組進行全量替換双饥,達到加載新的補丁的效果。

ART的冷啟動修復
ART的冷啟動方案是比較簡單的弟断,因為ART本身就支持多個dex加載咏花,當然多個dex加載也是存在一定順序的,首先需要加載classes.dex阀趴。正是基于這樣的加載順序昏翰,當patch.dex被下發(fā)到端上之后苍匆,只需要將其放到第一位,也就是將其文件名改為classes.dex棚菊,而將原來的文件名依次后移一位浸踩,然后重新執(zhí)行l(wèi)oadDex的加載過程,生成新的odex并全量替換原有的odex窍株,這樣就可以保證補丁包dex文件被優(yōu)先加載民轴,ART下的冷啟動修復就是這樣實現(xiàn)的。

[圖片上傳失敗...(image-b7d88-1596530601127)]

這里進行了一次整體的替換而不再只是某一個dex文件的插入球订,并且重新執(zhí)行了dex opt的過程后裸,所以這里不會出現(xiàn)預校驗的問題。同時以前和補丁包在同一個dex文件的這些類因為補丁包被打到了新的dex文件中冒滩,它們的預檢驗標簽會被去除掉微驶,但是由于這些類的數(shù)量很少,所以對于性能的影響也是比較小的开睡,做到了最大程度地降低了熱修復所帶來的性能開銷因苹。而因為loadDex的過程是非常耗時的,所以在真正實現(xiàn)的時候會做通過異步的方式另外開辟一個線程去做dex與odex的轉換篇恒。當App重新啟動扶檐,新的odex全部生成之后才會去做dexElement數(shù)組的替換,這就最大程度地保證了App運行的穩(wěn)定性胁艰。因為熱修復方案的目標是幫助修復問題款筑,所以不能再帶來其他的穩(wěn)定性問題,所以Sophix方案在這里花費了很大精力進行優(yōu)化腾么。

Dalvik的冷啟動修復
下表中除了Sophix還列舉了另外兩個方案進行對比來看奈梳。

[圖片上傳失敗...(image-ec183e-1596530601127)]

這里Qxxx方案原理是dexElement注入,特點是實現(xiàn)比較簡單解虱,但是由于基于插樁實現(xiàn)攘须,所以本身類加載的開銷比較大,所以不適用于生產環(huán)境殴泰。Txxx方案和Sophix都是全量地替換dex于宙,同時Txxx也不使用插樁的方式,最大程度地保障了性能艰匙,最具有的特點的就是自研了dex合并算法限煞,最大限度地減少了代碼體積,而這一點是它的優(yōu)勢也是它的劣勢员凝,需要比較細粒度地做dex合并署驻,從而可以把多個dex合并成為一個dex再去加載。但是這個自研的dex合并算法比較復雜,然而帶來的收益卻并不大旺上,雖然減少了代碼體積瓶蚂,但是一個App的體積里面的大部分并不是代碼本身而是資源,所以通過合并算法減少的代碼體積只是很少的一部分宣吱,所以性價比很低窃这。而在Sophix里面,雖然也做了全量的替換征候,但是相對而言比較簡單杭攻,以類為粒度合并dex,雖然沒有減少dex文件的體積疤坝,但是合并的方法更加簡單明了兆解。

在冷啟動修復下,Sophix方案有一個很簡單的思想就是當發(fā)現(xiàn)某些類存在Bug下發(fā)新的補丁之后跑揉,如果把原有的存在Bug的類從原來所屬的dex摳出來再去執(zhí)行加載的時候锅睛,因為原有的dex文件不再有這些類了,此時就會去從patch.dex文件中加載到它历谍,這樣就可以實現(xiàn)熱修復的效果现拒,并且這樣并不會有預檢驗的問題,從而最大程度地保證了程序性能望侈。所以這個方案中最困難的一點就是如何把以前這些有Bug的類從dex中摳出來印蔬,這個問題可能會非常復雜,因為每個類的大小都不一樣脱衙,如何將其從連續(xù)排列的內存空間中取出來然后再去做移位操作扛点,這樣想起來很復雜。而實際上Sophix使用了一個很巧妙的方式實現(xiàn)這樣的事情岂丘。

[圖片上傳失敗...(image-ba510d-1596530601127)]

在dex文件中其實會有一個叫做dexHeader的結構,這就是dex文件的文件頭眠饮,在這里面有兩個成員與實現(xiàn)緊密相關:一個叫做classDefsSize奥帘,另一個叫做classDefsOff。ClassDef也就是所有的類定義仪召,這個dex文件里面所有涉及到的類都會被注冊到classDef的結構體里面寨蹋,classDefsOff指的就是classDef結構的偏移量,所以通過classDefsOff就可以找到對應的dex文件里面的classDef扔茅,從而找到這個dex文件中到底有哪些類已旧,虛擬機也是同樣的原理,通過classDefsOff偏移量找到classDef再遍歷并加載相應的類召娜。而想要實現(xiàn)熱修復只需要在classDef里面將需要被替換的類抹掉就可以了运褪。所以在Dalvik下面的邏輯就是通過DexHeader找到classDefsOff,再在classDefs里面找到需要修改的類,把這些類抹掉再去修改classDefsSize秸讹,這樣當虛擬機再去加載dex文件的時候就會認為被修復的類不包含在dex文件里面檀咙,盡管實際上這些類的實現(xiàn)還是在dex文件里面的。

[圖片上傳失敗...(image-4aa2f1-1596530601127)]

3.3 Sophix資源修復原理解析
之前在Instant Run里面也提到了資源修復璃诀,因為資源修復下發(fā)的是全量的資源包弧可,所以并不適合在線上的環(huán)境中應用。想要在線上環(huán)境做資源修復肯定會使用差量的資源包劣欢,下發(fā)更新過的資源棕诵,而Sophix也是這樣做的。

[圖片上傳失敗...(image-41885-1596530601127)]

之前提到所有資源的訪問都是被AssetManager類所代理的凿将,AssetManager里面會有一個AssetPath數(shù)組校套,其中會有兩個成員,一個是指向系統(tǒng)框架資源丸相,一個應用的指向資源搔确。Sophix資源修復的思路就是在AssetPath數(shù)組里面多加一個資源,這樣當在前兩個資源路徑中都找不到時候就可以從新的資源路徑中尋找灭忠,可以通過這樣的方式去修復資源替換的問題膳算。當資源修改之后會下發(fā)一個資源修復的補丁包,同時把補丁包集成到端上之后會在AssetManager里面通過addAssetPath方法添加一個新的成員弛作,進而實現(xiàn)資源修復涕蜂。

而Sophix的資源修復會涉及到以下三種情況:

  • 新增資源導致原有資源id偏移:對比新舊代碼前,將新包中所引用的未修改資源ID修正映琳。
  • 引用內容修改的資源:對比新舊代碼前机隙,在新包中將所引用的原有資源ID置為更新后的ID。
  • 刪除資源:無需修改。

[圖片上傳失敗...(image-92c23b-1596530601127)]

四、熱修復方案的總結和對比

[圖片上傳失敗...(image-2e42c5-1596530601127)]

最后來總結和對比各種修復方案的原理和特點:

  • Qxxx方案原理比較簡單贰健,通過代碼插樁繞開預校驗問題梨熙,此外通過dexElement插入的方式使得帶補丁的dex文件優(yōu)先加載。其優(yōu)點在于實現(xiàn)比較簡單,可以修復大部分類層面的問題。但是同時其問題也是比較突出的,第一點是不支持實時生效娱俺,第二點就是全量插樁的方式侵入性非常強,同時性能損耗也是非常大的废麻。
  • Instant Run方案原理同樣用到了代碼插樁荠卷,而且其插樁比Qxxx方案更加復雜,它還會用到宿主的application做很多準備工作烛愧,這之后才會去執(zhí)行業(yè)務代碼油宜,最后它還會去通過AssetManager重建做資源修復工作掂碱。優(yōu)點是它能同時支持方法更新、類更新和資源更新验庙,并且在方法更新的過程中還可以做到及時修復顶吮,不需要重啟App。其問題在于還是使用了全量插樁粪薛,所以侵入性很強悴了,同時對于性能的損耗也很大,由于在進行修復時需要下發(fā)全量資源包违寿,所以開銷非常大湃交,同時也不適合在實際的生產環(huán)境中使用。
  • Andfix方案的原理是native方法的替換藤巢,這個方法很巧妙搞莺,并且實現(xiàn)比較簡單,而且可以做到及時生效掂咒。但是它不支持類結構的改變才沧,同時因為不同版本虛擬機的方法體結構不同,無法實現(xiàn)兼容性的處理绍刮,所以這種方案的兼容性也比較差温圆。
  • Sophix方案使用了很多很巧妙的原理實現(xiàn),首先它還是使用native方法替換孩革,這種方法會比Andfix更加巧妙岁歉,它不需要知道方法結構體具體的成員變量,而直接使用整體的替換膝蜈,只需要知道方法結構體的大小即可锅移。它使用了很巧妙的方式,通過兩個緊密排列的方法的地址差完成了方法替換即時生效的功能饱搏。Sophix使用全量dex替換去完成冷啟動修復場景非剃,而在資源修復的時候使用了差量資源包注入的方式,最大限度地降低了網絡的開銷推沸,只需要很輕量地把差量資源包下發(fā)就可以了努潘。其優(yōu)點在于同時支持方法更新、類更新和資源更新坤学,而且包括native方法的替換以及資源包的注入等很多實現(xiàn)非常巧妙和優(yōu)雅,也非常輕量报慕,并且屬于非侵入式的修復深浮。
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市眠冈,隨后出現(xiàn)的幾起案子飞苇,更是在濱河造成了極大的恐慌菌瘫,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,941評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件布卡,死亡現(xiàn)場離奇詭異雨让,居然都是意外死亡,警方通過查閱死者的電腦和手機忿等,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評論 3 395
  • 文/潘曉璐 我一進店門栖忠,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人贸街,你說我怎么就攤上這事庵寞。” “怎么了薛匪?”我有些...
    開封第一講書人閱讀 165,345評論 0 356
  • 文/不壞的土叔 我叫張陵捐川,是天一觀的道長。 經常有香客問我逸尖,道長古沥,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,851評論 1 295
  • 正文 為了忘掉前任娇跟,我火速辦了婚禮岩齿,結果婚禮上,老公的妹妹穿的比我還像新娘逞频。我一直安慰自己纯衍,他們只是感情好,可當我...
    茶點故事閱讀 67,868評論 6 392
  • 文/花漫 我一把揭開白布苗胀。 她就那樣靜靜地躺著襟诸,像睡著了一般。 火紅的嫁衣襯著肌膚如雪基协。 梳的紋絲不亂的頭發(fā)上歌亲,一...
    開封第一講書人閱讀 51,688評論 1 305
  • 那天,我揣著相機與錄音澜驮,去河邊找鬼陷揪。 笑死,一個胖子當著我的面吹牛杂穷,可吹牛的內容都是我干的悍缠。 我是一名探鬼主播,決...
    沈念sama閱讀 40,414評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼耐量,長吁一口氣:“原來是場噩夢啊……” “哼飞蚓!你這毒婦竟也來了?” 一聲冷哼從身側響起廊蜒,我...
    開封第一講書人閱讀 39,319評論 0 276
  • 序言:老撾萬榮一對情侶失蹤趴拧,失蹤者是張志新(化名)和其女友劉穎溅漾,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體著榴,經...
    沈念sama閱讀 45,775評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡添履,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了脑又。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片暮胧。...
    茶點故事閱讀 40,096評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖挂谍,靈堂內的尸體忽然破棺而出叔壤,到底是詐尸還是另有隱情,我是刑警寧澤口叙,帶...
    沈念sama閱讀 35,789評論 5 346
  • 正文 年R本政府宣布炼绘,位于F島的核電站,受9級特大地震影響妄田,放射性物質發(fā)生泄漏俺亮。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,437評論 3 331
  • 文/蒙蒙 一疟呐、第九天 我趴在偏房一處隱蔽的房頂上張望脚曾。 院中可真熱鬧,春花似錦启具、人聲如沸本讥。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,993評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽拷沸。三九已至,卻和暖如春薯演,著一層夾襖步出監(jiān)牢的瞬間撞芍,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,107評論 1 271
  • 我被黑心中介騙來泰國打工跨扮, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留序无,地道東北人。 一個月前我還...
    沈念sama閱讀 48,308評論 3 372
  • 正文 我出身青樓衡创,卻偏偏與公主長得像帝嗡,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子璃氢,可洞房花燭夜當晚...
    茶點故事閱讀 45,037評論 2 355