熱修復初探

熱修復這個技術點最近有點火,有QQ空間開發團隊爲其背書,還有的大廠開源的熱修復框架,這些對於推進這項技術也起了很大的做用。做爲一個有追求的工程師菜鳥,今天,我想經過幾篇文章把這種在線修復的解決思路以及幾種具體的實現方案理一遍。java

在開始分析以前,首先須要說說熱修復解決了一個什麼問題,閉上眼睛,想象一個場景:當你的產品剛上線,發現有一個致使閃退的空指針異常的問題或者程序重大漏洞急需解決,那麼問題來了,怎麼修復?常規的,固然是修改完有問題的方法,而後趕忙再打包,推送到用戶強制更新,可是,如今還有另外一種方式,就是程序在線下載修復文件到本地,而後用修改過的類覆蓋原來有問題的類,這樣的用戶體驗是一顆賽艇的。android

好了,回到熱修復這個話題。網絡

首先我想先談談熱修復自己,熱修復是一種動態修復程序解決問題的思想,其自己是有不少不一樣的具體實現方案的,阿里的基於C/C++層操控method指針的Dexposed,AndFix,以及QQ空間的基於dex分包的HotFix,後者和前者的熱修復方案在原理上大相徑庭,能夠說各有千秋。而我在查閱資料的時候,發現不少Blog都不夠嚴謹,每每標題聲稱熱修復技術可是隻解釋QQ空間的解決方案,能夠說這種作法是容易誤導人的,雖然不能算錯誤,可是不太嚴謹。框架

目前的熱修復技術的解決方案有不少,我想就上面提到的兩種解決方案來作詳細的探討。ide

1,基於C層指針替換的Dexposed和AndFix

  • 這兩個熱修復的框架,在底層原理上是基本一致的,因此我想把他們放在一塊兒探討,

他們都作了大體三件事:優化

  • 1,在C/C++層將Java層中出問題的方法修改成native方法
  • 2,獲取問題方法call到C層的指針
  • 3,經過獲取的指針作相應的操做:調用Java層的回調方法繼續處理(DexPosed)或者直接經過反射調用Java層的補丁方法(AndFix)。

以Dexposed爲例:spa

dexposed.jpg

至於具體的代碼解釋,請直接看Android中免Root實現Hook的Dexposed框架實現原理解析以及如何實現應用的熱修復3d

這兩種熱修復框架的區別在於:指針

  • Dexposed暫時不支持ART模式,AndFix支持
  • AndFix方案更加成熟,更加自動化(畢竟是支付寶出的)

2,基於Dex分包的HotFix

這個解決方案很巧妙,基於Google推出的的Multidex方案,以ClassLoader的方式完成對問題類的替換。code

因此這個問題必定會先談Android的分包方案:爲了解決Android4.x系統中65536的方法數限制,Android推出Multidex方案,將一個完整的APK中的Dex拆分紅好幾個dex,經過PathClassLoader 這個加載器來加載。

當點開程序的時候,PathClassLoader 會把分包的多個dex添加到父類中的一個DexPathList 中

DexPathList 詳情以下:

public class BaseDexClassLoader extends ClassLoader {

    private final DexPathList pathList;
}複製代碼
/*package*/ final class DexPathList {
    private static final String DEX_SUFFIX = ".dex";
    private static final String JAR_SUFFIX = ".jar";
    private static final String ZIP_SUFFIX = ".zip";
    private static final String APK_SUFFIX = ".apk";

    /** class definition context */
    private final ClassLoader definingContext;

    /** list of dex/resource (class path) elements 也就是dex列表咯*/
    private final Element[] dexElements;

    /** list of native library directory elements */
    private final File[] nativeLibraryDirectories;複製代碼

那麼當須要加載某個類的時候,是怎麼加載的呢?

//BaseDexClassLoader: 
    @Override  
    protected Class< ?> findClass(String name) throws ClassNotFoundException {  
        List<Throwable> suppressedExceptions = new ArrayList<Throwable>(); 
        Class c = pathList.findClass(name, suppressedExceptions);  
        if (c == null) {  
            ClassNotFoundException cnfe = new ClassNotFoundException("Didn't find class \"" + name + "\" on path: " + pathList);  
            for (Throwable t : suppressedExceptions) {  
                cnfe.addSuppressed(t);  
           }  
        throw cnfe;  
        }  
        return c;  
    }複製代碼

findClass()方法以下:

public Class findClass(String name, List<Throwable> suppressed) {      
         for (Element element : dexElements) {  
           DexFile dex = element.dexFile;  
            if (dex != null) {  
                Class clazz = dex.loadClassBinaryName(name, definingContext, suppressed);  
                if (clazz != null) {  
                    return clazz;  
                }  
            }  
       }  
        if (dexElementsSuppressedExceptions != null) {  
            suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));  
        }  
        return null;  
    }複製代碼

如你所見,當須要加載一個類的時候,會在pathList中去尋找,而且是經過順序遍歷各個dex包的方式,一旦找到目標類,則中止遍歷

qqZone.png

這就給了咱們一個想法,有沒有可能把打了補丁的dex插入到pathList中,當須要加載有問題的類的時候,根據遍歷,首先查到已經修復的類,遍歷結束,也就完成了修復。(固然了,這個想法是騰訊空間Android工程師想到的)

有了想法,也得有合適的加載器啊。結果你猜怎麼着?Android還真提供了這樣的機會。

在Android中也有三個類加載器,分別是UrlClassLoader,PathClassLoader,DexClassLoader.

  • UrlClassLoader 從Url列表中加載相關的jar文件,可是dalvik沒法直接識別jar,so.....
  • PathClassLoader 它只會去讀取 /data/dalvik-cache 目錄下的 dex 文件,就是已安裝的apk,
  • DexClassLoader 能夠用來從.jar和.apk類型的文件內部加載classes、dex文件。並且,它和PathClassLoader繼承自共同的父類。顯然,這是最合適的加載器。

android.png

好了,基本機制到這裏就結束了,還有一些問題卻沒有被提出來,不過網絡上已經有了很好的解決方案了。

  • 如何防止本身的類被打上 CLASS_ISPREVERIFIED標誌
    • 這個標誌是虛擬機的一種優化手段,打上這個標誌以後,就不會引用其餘dex中的類,若是引用了,則報錯。解決方案也很簡單,就是在類中引用其餘dex包的引用,具體方法請直接Google。

雖然如今咱們公司的開發團隊確定用不上熱修復技術,可是做爲工程師卻必須對新技術有所研究。近期我會繼續研究熱修復 HotFix 框架的源碼,有必要的話會對DexClassLoader如何動態的插入jar包或者dex文件給出更詳細的解析,暫時沒有時間解釋了,你們先上車

臥槽 說錯話了.....

相關文章
相關標籤/搜索