QQ空間 安卓App熱補丁動態修復技術介紹

【原文地址 點擊打開鏈接

【各自熱修復框架介紹 點擊打開鏈接

【AndFix使用說明 AndFix使用說明

AndFix與Nuwa對比

Nuwa是另一個熱補丁框架,原理是基於QQ空間團隊提出的安卓App熱補丁動態修復技術介紹
與Nuwa相比,AndFix有一下優點:

  • 1、不需要重啓APP即可應用補丁。
  • 2、安全性更好,Nuwa後面的版本應該也會加上安全方面的內容。

但是也有缺點

  • 1、無法添加類和字段
  • 2、Nuwa成功率高

線上程序出現bug,必須要有妙手回春的本事,想成爲一名Andorid高工,這是必備技能。
Android 熱補丁動態修復框架小結
Android熱補丁動態修復技術系列 
安卓App熱補丁動態修復技術介紹

現在市面上熱修復框架很多,以下是常見的四種。

  Tinker QZone AndFix Dexposed
類替換 yes yes no no
So替換 yes no no no
資源替換 yes yes no no
全平臺支持 yes yes yes no
即時生效 no no yes yes
性能損耗 較小 較大 較小 較小
補丁包大小 較小 較大 一般 一般
開發透明 yes yes no no
複雜度 較低 較低 複雜 複雜
gradle支持 yes no no no
接口文檔 豐富 較少 一般 較少
Rom體積 Dalvik較大 較小 較小 較小
成功率 較高 最高 一般 一般

總的來說:

  1. Dexposed無法支持全平臺,並不適合應用到商業產品中。
  2. AndFix作爲native解決方案,首先面臨的是穩定性與兼容性問題,更重要的是它無法實現類替換,它是需要大量額外的開發成本的。
  3. QZone方案主要問題是插樁帶來Dalvik的性能問題,以及爲了解決Art下內存地址問題而導致補丁包急速增大的。
    Tinker熱補丁方案不僅支持類、So以及資源的替換,它還是2.X-7.X的全平臺支持。它無需插樁,補丁大小也遠遠小於其他方案。Tinker已運行在微信的數億Android設備上。

下面這個項目是一個基於熱修復框架TinkerDemo,並總結了一些常見的問題。


tinker.gif

項目地址https://github.com/xinghongfei/Hello-Tinker


1.背景

當一個App發佈之後,突然發現了一個嚴重bug需要進行緊急修復,這時候公司各方就會忙得焦頭爛額:重新打包App、測試、向各個應用市場和渠道換包、提示用戶升級、用戶下載、覆蓋安裝。有時候僅僅是爲了修改了一行代碼,也要付出巨大的成本進行換包和重新發布。

這時候就提出一個問題:有沒有辦法以補丁的方式動態修復緊急Bug,不再需要重新發布App,不再需要用戶重新下載,覆蓋安裝?

雖然Android系統並沒有提供這個技術,但是很幸運的告訴大家,答案是:可以,我們QQ空間提出了熱補丁動態修復技術來解決以上這些問題。


2.實際案例

空間Android獨立版5.2發佈後,收到用戶反饋,結合版無法跳轉到獨立版的訪客界面,每天都較大的反饋。在以前只能緊急換包,重新發布。成本非常高,也影響用戶的口碑。最終決定使用熱補丁動態修復技術,向用戶下發Patch,在用戶無感知的情況下,修復了外網問題,取得非常好的效果。


3.解決方案

該方案基於的是android dex分包方案的,關於dex分包方案,網上有幾篇解釋了,所以這裏就不再贅述,具體可以看這裏https://m.oschina.net/blog/308583。

簡單的概括一下,就是把多個dex文件塞入到app的classloader之中,但是android dex拆包方案中的類是沒有重複的,如果classes.dex和classes1.dex中有重複的類,當用到這個重複的類的時候,系統會選擇哪個類進行加載呢?

讓我們來看看類加載的代碼:


一個ClassLoader可以包含多個dex文件,每個dex文件是一個Element,多個dex文件排列成一個有序的數組dexElements,當找類的時候,會按順序遍歷dex文件,然後從當前遍歷的dex文件中找類,如果找類則返回,如果找不到從下一個dex文件繼續查找。

理論上,如果在不同的dex中有相同的類存在,那麼會優先選擇排在前面的dex文件的類,如下圖:


在此基礎上,我們構想了熱補丁的方案,把有問題的類打包到一個dex(patch.dex)中去,然後把這個dex插入到Elements的最前面,如下圖:


好,該方案基於第二個拆分dex的方案,方案實現如果懂拆分dex的原理的話,大家應該很快就會實現該方案,如果沒有拆分dex的項目的話,可以參考一下谷歌的multidex方案實現。然後在插入數組的時候,把補丁包插入到最前面去。

好,看似問題很簡單,輕鬆的搞定了,讓我們來試驗一下,修改某個類,然後打包成dex,插入到classloader,當加載類的時候出現了(本例中是QzoneActivityManager要被替換):


爲什麼會出現以上問題呢?

從log的意思上來講,ModuleManager引用了QzoneActivityManager,但是發現這這兩個類所在的dex不在一起,其中:

1. ModuleManager在classes.dex中

2. QzoneActivityManager在patch.dex中

結果發生了錯誤。

這裏有個問題,拆分dex的很多類都不是在同一個dex內的,怎麼沒有問題?

讓我們搜索一下拋出錯誤的代碼所在,嘿咻嘿咻,找到了一下代碼:


從代碼上來看,如果兩個相關聯的類在不同的dex中就會報錯,但是拆分dex沒有報錯這是爲什麼,原來這個校驗的前提是:

如果引用者(也就是ModuleManager)這個類被打上了CLASS_ISPREVERIFIED標誌,那麼就會進行dex的校驗。那麼這個標誌是什麼時候被打上去的?讓我們在繼續搜索一下代碼,嘿咻嘿咻~~,在DexPrepare.cpp找到了一下代碼:


這段代碼是dex轉化成odex(dexopt)的代碼中的一段,我們知道當一個apk在安裝的時候,apk中的classes.dex會被虛擬機(dexopt)優化成odex文件,然後纔會拿去執行。
虛擬機在啓動的時候,會有許多的啓動參數,其中一項就是verify選項,當verify選項被打開的時候,上面doVerify變量爲true,那麼就會執行dvmVerifyClass進行類的校驗,如果dvmVerifyClass校驗類成功,那麼這個類會被打上CLASS_ISPREVERIFIED的標誌,那麼具體的校驗過程是什麼樣子的呢?

此代碼在DexVerify.cpp中,如下:


1. 驗證clazz->directMethods方法,directMethods包含了以下方法:

1. static方法

2. private方法

3. 構造函數

2.clazz->virtualMethods

1. 虛函數=override方法?

概括一下就是如果以上方法中直接引用到的類(第一層級關係,不會進行遞歸搜索)和clazz都在同一個dex中的話,那麼這個類就會被打上CLASS_ISPREVERIFIED


所以爲了實現補丁方案,所以必須從這些方法中入手,防止類被打上CLASS_ISPREVERIFIED標誌

最終空間的方案是往所有類的構造函數裏面插入了一段代碼,代碼如下:

if (ClassVerifier.PREVENT_VERIFY) {

System.out.println(AntilazyLoad.class);

}


其中AntilazyLoad類會被打包成單獨的hack.dex,這樣當安裝apk的時候,classes.dex內的類都會引用一個在不相同dex中的AntilazyLoad類,這樣就防止了類被打上CLASS_ISPREVERIFIED的標誌了,只要沒被打上這個標誌的類都可以進行打補丁操作。

然後在應用啓動的時候加載進來.AntilazyLoad類所在的dex包必須被先加載進來,不然AntilazyLoad類會被標記爲不存在即使後續加載了hack.dex包那麼他也是不存在的這樣屏幕就會出現茫茫多的類AntilazyLoad找不到的log。

所以Application作爲應用的入口不能插入這段代碼。(因爲載入hack.dex的代碼是在Application中onCreate中執行的,如果在Application的構造函數裏面插入了這段代碼,那麼就是在hack.dex加載之前就使用該類,該類一次找不到,會被永遠的打上找不到的標誌)

其中:


之所以選擇構造函數是因爲他不增加方法數,一個類即使沒有顯式的構造函數,也會有一個隱式的默認構造函數。

空間使用的是在字節碼插入代碼,而不是源代碼插入,使用的是javaassist庫來進行字節碼插入的。

隱患:

虛擬機在安裝期間爲類打上CLASS_ISPREVERIFIED標誌是爲了提高性能的,我們強制防止類被打上標誌是否會影響性能?這裏我們會做一下更加詳細的性能測試.但是在大項目中拆分dex的問題已經比較嚴重,很多類都沒有被打上這個標誌。

如何打包補丁包:

1. 空間在正式版本發佈的時候,會生成一份緩存文件,裏面記錄了所有class文件的md5,還有一份mapping混淆文件。

2. 在後續的版本中使用-applymapping選項,應用正式版本的mapping文件,然後計算編譯完成後的class文件的md5和正式版本進行比較,把不相同的class文件打包成補丁包。

備註:該方案現在也應用到我們的編譯過程當中,編譯不需要重新打包dex,只需要把修改過的類的class文件打包成patch dex,然後放到sdcard下,那麼就會讓改變的代碼生效。



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