1. 程式人生 > >android熱修復原理總結

android熱修復原理總結

背景

當app釋出之後如果出現了緊急的線上bug,整個公司都會為此忙的焦頭爛額,現公司如果線上出現嚴重的P1級bug,甚至大半夜整個專案組都得來緊急修復上線,而bug的原因可能僅僅是傳錯了引數,或者寫錯一行程式碼,而且修復後的app又得重新上架,直到使用者更新後bug才會被修正。那熱修復技術的出現就能很大程度上緩解這種情況,修復後不需要重新上架,使用者也不需要重新下載安裝。

原理

github上的熱修復框架如nuwa,HotFix原理都是依據安卓App熱補丁動態修復技術介紹Android dex分包方案
這兩篇文章,我這裡也只是對這兩篇文章做一個自己的總結加深理解。
關於nuwa框架的使用看另一篇部落格

熱修復框架nuwa的使用
熱修復原理是基於Android的分包方案的,那麼什麼是Android的分包方案呢,Android2.3之前執行dexopt的記憶體只有5M,每個dex的方法數不能超過65535,當app功能複雜,類和方法特別多的時候就會在編譯時報錯。Android dex分包方案中的描述:

當一個app的功能越來越複雜,程式碼量越來越多,也許有一天便會突然遇到下列現象:

  1. 生成的apk在2.3以前的機器無法安裝,提示INSTALL_FAILED_DEXOPT

  2. 方法數量過多,編譯時出錯,提示:

Conversion to Dalvik format failed:Unable to execute dex: method ID
not in [0, 0xffff]: 65536

出現這種問題的原因是:

  1. Android2.3及以前版本用來執行dexopt(用於優化dex檔案)的記憶體只分配了5M

  2. 一個dex檔案最多隻支援65536個方法。

解決辦法是將編譯好的class檔案打成兩個dex的包,執行時注入ClassLoader。如何注入呢,先看一下Android的ClassLoader體系,圖片是分包方案裡扒的。
這裡寫圖片描述
可以看到實現類有兩個DexClassLoader和PathClassLoader,其中PathClassLoader是用來Android用來載入Android系統類和應用的載入器,DexClassLoader用來載入.dex和.jar中的class.dex檔案。看一下BaseDexClassLoader載入類的方法,從pathList里根據類名找,找不到就class not found。pathList是BaseDexClassLoader中的一個物件,它包含一個dexElements集合,找類就是聽過遍歷這個集合,拿到dexFile去找類。

一個ClassLoader可以包含多個dex檔案,每個dex檔案是一個Element,多個dex檔案排列成一個有序的陣列dexElements,當找類的時候,會按順序遍歷dex檔案,然後從當前遍歷的dex檔案中找類,如果找類則返回,如果找不到從下一個dex檔案繼續查詢。(來自:安卓App熱補丁動態修復技術介紹)

#BaseDexClassLoader
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
    Class clazz = pathList.findClass(name);

    if (clazz == null) {
        throw new ClassNotFoundException(name);
    }

    return clazz;
}

#DexPathList
public Class findClass(String name) {
    for (Element element : dexElements) {
        DexFile dex = element.dexFile;

        if (dex != null) {
            Class clazz = dex.loadClassBinaryName(name, definingContext);
            if (clazz != null) {
                return clazz;
            }
        }
    }

    return null;
}

#DexFile
public Class loadClassBinaryName(String name, ClassLoader loader) {
    return defineClass(name, loader, mCookie);
}
private native static Class defineClass(String name, ClassLoader loader, int cookie);

這樣我們將有bug的類打成patch.jar然後插入到dexElements的最前邊位置,findclass的時候就會從我們的patch.jar 中開始找,找到類之後就返回,有問題的類就被補丁包中的替換掉了。之後還有一個CLASS_ISPREVERIFIED的問題,詳細可以看安卓App熱補丁動態修復技術介紹主要是說,如果類和其引用類如果不在同一個dex包裡就會報錯,校檢出這個錯的前提是引用類被打上了CLASS_ISPREVERIFIED標識,這個標識是什麼時候被打上的呢,在虛擬機器啟動的時候如果一個類的private,static,或者構造方法直接引用到的類都在同一個dex包裡就會給當前類打上這個標識,所以要阻止這個表示被打上,我們要讓類引用一個外部dex包裡的類,往所有的類的建構函式中插入一段

if (ClassVerifier.PREVENT_VERIFY) {
    System.out.println(AntilazyLoad.class);
}
public class AntilazyLoad
{

}

所以我們還需要一個hack.dex,寫個空的類AntilazyLoad打成dex包,而且必須先載入這個包,否則引用這個類的地方就會class not fount,載入方法就和之前的一樣啦,要注意

Application作為應用的入口不能插入這段程式碼。(因為載入hack.dex的程式碼是在Application中onCreate中執行的,如果在Application的建構函式裡面插入了這段程式碼,那麼就是在hack.dex載入之前就使用該類,該類一次找不到,會被永遠的打上找不到的標誌)(來自:安卓App熱補丁動態修復技術介紹)。