版權聲明:本文由johncz原創文章,轉載請註明出處:
文章原文連接:https://www.qcloud.com/community/article/169java
來源:騰雲閣 https://www.qcloud.com/communityandroid
當一個App發佈以後,忽然發現了一個嚴重bug須要進行緊急修復,這時候公司各方就會忙得焦頭爛額:從新打包App、測試、向各個應用市場和渠道換包、提示用戶升級、用戶下載、覆蓋安裝。有時候僅僅是爲了修改了一行代碼,也要付出巨大的成本進行換包和從新發布。
這時候就提出一個問題:有沒有辦法以補丁的方式動態修復緊急Bug,再也不須要從新發布App,再也不須要用戶從新下載,覆蓋安裝?
雖然Android系統並無提供這個技術,可是很幸運的告訴你們,答案是:能夠,咱們QQ空間提出了熱補丁動態修復技術來解決以上這些問題。數組
空間Android獨立版5.2發佈後,收到用戶反饋,結合版沒法跳轉到獨立版的訪客界面,天天都較大的反饋。在之前只能緊急換包,從新發布。成本很是高,也影響用戶的口碑。最終決定使用熱補丁動態修復技術,向用戶下發Patch,在用戶無感知的狀況下,修復了外網問題,取得很是好的效果。緩存
該方案基於的是android dex分包方案的,關於dex分包方案,網上有幾篇解釋了,因此這裏就再也不贅述,具體能夠看這裏
簡單的歸納一下,就是把多個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不在一塊兒,其中:app
讓咱們搜索一下拋出錯誤的代碼所在,嘿咻嘿咻,找到了一下代碼:
從代碼上來看,若是兩個相關聯的類在不一樣的dex中就會報錯,可是拆分dex沒有報錯這是爲何,原來這個校驗的前提是:
ide
若是引用者(也就是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中,以下:
函數
歸納一下就是若是以上方法中直接引用到的類(第一層級關係,不會進行遞歸搜索)和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的問題已經比較嚴重,不少類都沒有被打上這個標誌。
如何打包補丁包:測試