天天看點

Android熱修複技術選型——三大流派解析

文章作者:所為 淘寶無線開發專家

2015年以來,android開發領域裡對熱修複技術的讨論和分享越來越多,同時也出現了一些不同的解決方案,如qq空間更新檔方案、阿裡andfix以及微信tinker,它們在原理各有不同,适用場景各異,到底采用哪種方案,是開發者比較頭疼的問題。本文希望通過介紹qq空間更新檔、tinker以及基于andfix的阿裡百川hotfix技術的原理分析和橫向比較,幫助開發者更深入了解熱修複方案。

Android熱修複技術選型——三大流派解析

從流程來看,傳統的開發流程存在很多弊端:

重新釋出版本代價太大

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

bug修複不及時,使用者體驗太差

Android熱修複技術選型——三大流派解析

而熱修複的開發流程顯得更加靈活,優勢很多:

無需重新發版,實時高效熱修複

使用者無感覺修複,無需下載下傳新的應用,代價小

修複成功率高,把損失降到最低

熱修複作為當下熱門的技術,在業界内比較著名的有阿裡巴巴的andfix、dexposed,騰訊qq空間的超級更新檔技術和微信的tinker。最近阿裡百川推出的hotfix熱修複服務就基于andfix技術,定位于線上緊急bug的即時修複,是以andfix技術這塊我們重點分析阿裡百川hotfix。下面,我們就分别介紹qq空間超級熱更新檔技術和微信的tinker以及阿裡百川hotfix技術。

超級更新檔技術基于dex分包方案,使用了多dex加載的原理,大緻的過程就是:把bug方法修複以後,放到一個單獨的dex裡,插入到dexelements數組的最前面,讓虛拟機去加載修複完後的方法。

Android熱修複技術選型——三大流派解析

當patch.dex中包含test.class時就會優先加載,在後續的dex中遇到test.class的話就會直接傳回而不去加載,這樣就達到了修複的目的。

但是有一個問題是,當兩個調用關系的類不在同一個dex時,就會産生異常報錯。我們知道,在apk安裝時,虛拟機需要将classes.dex優化成odex檔案,然後才會執行。在這個過程中,會進行類的verify操作,如果調用關系的類都在同一個dex中的話就會被打上class_ispreverified的标志,然後才會寫入odex檔案。

是以,為了可以正常的進行打更新檔修複,必須避免類被打上class_ispreverified标志,具體的做法就是單獨放一個類在另外dex中,讓其他類調用。

我們來逆向手機qq空間apk看一下具體的實作:

先進入程式入口qzonerealapplication,在attachbasecontext中進行了兩步操作:修複class_ispreverified标志導緻的unexpected dex problem異常、加載修複的dex。

Android熱修複技術選型——三大流派解析

1. 修複unexpecteddex problem異常

先看代碼:

Android熱修複技術選型——三大流派解析

可以看到,這裡是要加載一個libs目錄下的dalvikhack.jar。在項目的assets/libs找到該檔案,解壓得到classes.dex檔案,逆向打開該dex檔案,

Android熱修複技術選型——三大流派解析

通過不同的dex加載進來,然後在每一個類的構造方法中引用其他dex中的唯一類anitlazyload,避免類被打上class_ispreverified标志。

Android熱修複技術選型——三大流派解析

在無修複的情況下,将do_verify_classes設定為false,提高性能。隻有在需要修複的時候,才設定為true。

Android熱修複技術選型——三大流派解析

至于如何加載進來,與接下來第二個步驟基本相同。

2. 加載修複的dex

從loadpatchdex()方法進入,經過幾次跳轉,到達核心的代碼段,systemclassloaderinjector.c()。由于進行了混淆和多次方法的跳轉,于是将核心代碼段做了如下整理:

Android熱修複技術選型——三大流派解析

修複的步驟為:

1. 可以看出是通過擷取到目前應用的classloader,即為basedexclassloader

2. 通過反射擷取到他的dexpathlist屬性對象pathlist

3. 通過反射調用pathlist的dexelements方法把patch.dex轉化為element[]

4. 兩個element[]進行合并,把patch.dex放到最前面去

5. 加載element[],達到修複目的

整體的流程圖如下:

Android熱修複技術選型——三大流派解析

從流程圖來看,可以很明顯的找到這種方式的特點:

優勢:

沒有合成整包(和微信tinker比起來),産物比較小,比較靈活

可以實作類替換,相容性高。(某些三星手機不起作用)

不足:

1. 不支援即時生效,必須通過重新開機才能生效。

2. 為了實作修複這個過程,必須在應用中加入兩個dex!dalvikhack.dex中隻有一個類,對性能影響不大,但是對于patch.dex來說,修複的類到了一定數量,就需要花不少的時間加載。對手淘這種航母級應用來說,啟動耗時增加2s以上是不能夠接受的事。

3. 在art模式下,如果類修改了結構,就會出現記憶體錯亂的問題。為了解決這個問題,就必須把所有相關的調用類、父類子類等等全部加載到patch.dex中,導緻更新檔包異常的大,進一步增加應用啟動加載的時候,耗時更加嚴重。

微信針對qq空間超級更新檔技術的不足提出了一個提供dex差量包,整體替換dex的方案。主要的原理是與qq空間超級更新檔技術基本相同,差別在于不再将patch.dex增加到elements數組中,而是差量的方式給出patch.dex,然後将patch.dex與應用的classes.dex合并,然後整體替換掉舊的dex,達到修複的目的。

Android熱修複技術選型——三大流派解析

我們來逆向微信apk看一下具體的實作:

先找到應用入口tinkerapplication,在onbasecontextattached()調用了loadtinker(),

Android熱修複技術選型——三大流派解析

進入tinkerloader的tryload()方法中,

Android熱修複技術選型——三大流派解析

從方法名可以預見,在tryloadpatchfilesinternal()中嘗試加載本地的更新檔,再經過跳轉進入核心修複功能類systemclassloaderadder.class中。

Android熱修複技術選型——三大流派解析

代碼中可以看出,根據android版本的不同,分别采取具體的修複操作,不過原理都是一樣的。我們以v19為例:

Android熱修複技術選型——三大流派解析

從代碼中可以看到,通過反射操作得到pathclassloader的dexpatchlist,反射調用patchlist的makedexelements()方法吧本地的dex檔案直接替換到element[]數組中去,達到修複的目的。

對于如何進行patch.dex與classes.dex的合并操作,這裡微信開啟了一個新的程序,開啟新程序的服務tinkerpatchservice進行合并。

Android熱修複技術選型——三大流派解析

整體的流程如下:

Android熱修複技術選型——三大流派解析

從流程圖來看,同樣可以很明顯的找到這種方式的特點:

合成整包,不用在構造函數插入代碼,防止verify,verify和opt在編譯期間就已經完成,不會在運作期間進行

性能提高。相容性和穩定性比較高。

開發者透明,不需要對包進行額外處理。

與超級更新檔技術一樣,不支援即時生效,必須通過重新開機應用的方式才能生效。

需要給應用開啟新的程序才能進行合并,并且很容易因為記憶體消耗等原因合并失敗。

合并時占用額外磁盤空間,對于多dex的應用來說,如果修改了多個dex檔案,就需要下發多個patch.dex與對應的classes.dex進行合并操作時這種情況會更嚴重,是以合并過程的失敗率也會更高。

阿裡百川推出的熱修複hotfix服務,相對于qq空間超級更新檔技術和微信tinker來說,定位于緊急bug修複的場景下,能夠最及時的修複bug,下拉更新檔立即生效無需等待。

Android熱修複技術選型——三大流派解析

1、andfix實作原理

andfix不同于qq空間超級更新檔技術和微信tinker通過增加或替換整個dex的方案,提供了一種運作時在native修改filed指針的方式,實作方法的替換,達到即時生效無需重新開機,對應用無性能消耗的目的。

原理圖如下:

Android熱修複技術選型——三大流派解析

2、andfix實作過程

對于實作方法的替換,需要在native層操作,經過三個步驟:

Android熱修複技術選型——三大流派解析

接下來以dalvik裝置為例,來分析具體的實作過程:

2.1 setup()

Android熱修複技術選型——三大流派解析

對于dalvik來說,遵循jit即時編譯機制,需要在運作時裝載libdvm.so動态庫,擷取以下内部函數:

1) dvmthreadself( ):查詢目前的線程;

2)dvmdecodeindirectref():根據目前線程獲得classobject對象。

2.2 setfieldflag

Android熱修複技術選型——三大流派解析

該操作的目的:讓private、protected的方法和字段可被動态庫看見并識别。原因在于動态庫會忽略非public屬性的字段和方法。

2.3 replacemethod

Android熱修複技術選型——三大流派解析

該步驟是方法替換的核心,替換的流程如下:

Android熱修複技術選型——三大流派解析

andfix對art裝置同樣支援,具體的過程與dalvik相似,這裡不再贅述。

從技術原理,不難看出阿裡百川hotfix的幾個特點:

最大的優勢在于

bug修複的即時性

更新檔包同樣采用差量技術,生成的patch體積小

對應用無侵入,幾乎無性能損耗

不支援新增字段,以及修改方法,也不支援對資源的替換。

由于廠商的自定義rom,對少數機型暫不支援。

綜合分析如下:

Android熱修複技術選型——三大流派解析

我們可以看到,qq空間超級更新檔技術和微信tinker的修複原理都基于類加載,在功能上已經支援類、資源的替換和新增,功能非常強大。既然已經有了這麼強大的熱修複技術,為什麼阿裡百川還要推出自己的熱修複方案hotfix呢?

我們知道,多dex方案用來解決應用方法數65k的問題,現在google也官方支援了multidex的實作方案。但是,這實在是應用因方法數超出而作出的不得已的下策,但是超級更新檔技術和tinker作為一種熱修複的方案,平生給應用增加了多個dex,而多dex技術最大的問題在于性能上的坑,是以基于這種方案的更新檔技術影響應用的性能是無疑的。

1. 啟動加載時間過長

我們可以看到,超級更新檔技術和tinker都選擇在application的attachbasecontext()進行更新檔dex的加載,即使這是加載dex的最佳時機,但是依然會帶來很大的性能問題,首當其沖的就是啟動時間太長。

對于更新檔dex來說,應用啟動時虛拟機會進行dexopt操作,将patch.dex檔案轉換成odex檔案,這個過程非常耗時。而這個過程,又要求需要在主線程中,以同步的方式執行,否則無法成功進行修複。就dex的加載時間,大概做了以下的時間測試。

Android熱修複技術選型——三大流派解析

随着patch.dex的增加,在不做任何優化的情況下,啟動時間也直線增長。對于一個應用來說,這簡直是災難性的。

2. 易造成應用的anr和crash

正是尤其多dex加載導緻了啟動時間過長,很容易就會引發應用的anr。我們知道當應用在主線程等待超過5s以後,就會直接導緻長時間無響應而退出。超級更新檔技術為保證art不出現位址錯亂問題,需要将所有關聯的類全部加入到更新檔中,而微信tinker采取一種差量包合并加載的方式,都會使要加載的dex體積變得很大。這也很大程度上容易導緻anr情況的出現。

除了應用anr以外,多dex模式也同樣很容易導緻crash情況的出現。我們知道,超級更新檔技術為了保證art裝置下不出現位址錯亂問題,需要把修改類的所有相關類全部加入到更新檔中,這裡會出現一個問題,為了保證更新檔包的體積最小,能否保證引入全部的關聯類而不引入無關的類呢?一旦沒有引入關聯的類,就會出現以下的異常:

noclassdeffounderror

could not find class

could not find method

出現這些異常,就會直接導緻應用的crash退出。

是以,不難看出如果我們需要修複一個不是crash的bug,但是因為未加入相關類而導緻了更嚴重的crash,就更加的得不償失。

總的來說,熱修複本質的目的是為了保證應用更加穩定,而不是為了更強大的功能引入更大的風險和不穩定性。

我們經常提到熱修複和插件化,這都是當下熱門的新興技術。在講述之前,需要對這兩個概念進行一下解釋。

插件化:一個程式劃分為不同的部分,以插件的形式加載到應用中去,本質上它使用的技術還是熱修複技術,隻是加入了更多工程實踐,讓它支援大規模的代碼更新以及資源和so包的更新。

熱修複:當線上應用出現緊急bug,為了避免重新發版,并且保證修複的及時性而進行的一項線上推送更新檔的修複方案。

顯然,從概念上我們可以看到,插件化使用場景更多是功能,熱修複使用常見在于修複。從這個層面來說,插件化必然功能更加強大,能做的事情也更多。qq空間超級更新檔技術和微信tinker從類、資源的替換和更新上來看,與其說是熱修複,不如說是插件化。

當然,強大的功能也就增加了不穩定的因素。比如上文提到的增加啟動時間,導緻anr、crash的問題。

qq空間超級更新檔技術和微信tinker提供了更加強大的功能,但是對應用的性能和穩定有較大的影響,就bug修複的這個使用場景上還不夠明确,并且顯得過重。

針對應用的性能損耗,我們可以舉例做一個對比。某app的啟動載入時間為3s左右,本身就是基于多dex模式的實作。

分别接入三種熱修複服務,根據騰訊提供超級更新檔技術和tinker的資料,那麼會變成以下的場景:

阿裡百川hotfix:啟動時間幾乎無增加,不增加運作期額外的磁盤消耗。

qq空間超級更新檔技術:如果應用有700個類,啟動耗時增加超過2.5s,達到5.5s以上。

微信tinker:假設應用有5個dex檔案,分别修改了這5個dex,産生5個patch.dex檔案,就要進行5次的patch合并動作,假設每個更新檔1m,那麼就要多占用7.5m的磁盤空間。

顯然對于修複緊急bug這個場景,阿裡百川hotfix的更為合适,它更加輕量,可以在不重新開機的情況下生效,且對性能幾乎沒有影響。微信tinker、qq空間超級更新檔技術更多地把場景定位在釋出小的新功能上,采用classloader的模式,犧牲較高的性能代價去實作類、資源新增或替換的功能。阿裡百川hotfix對應用本身做到無侵入,無性能損耗。

qq空間超級更新檔技術和微信tinker 支援新增類和資源的替換,在一些功能化的更新上更為強大,但對應用的性能和穩定會有的一定的影響;阿裡百川hotfix雖然暫時不支援新增類和資源的替換,對新功能的釋出也有所限制,但是作為一項定位為線上緊急bug的熱修複的服務來說,能夠真正做到bug即時修複使用者無感覺,同時保證對應用性能不産生不必要的損耗,在熱修複方面不失為一個好的選擇。

繼續閱讀