溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

還不懂熱修復(fù)的原理?看完這篇后帶你手把手寫代碼

發(fā)布時(shí)間:2020-03-03 03:39:03 來源:網(wǎng)絡(luò) 閱讀:232 作者:Android丶VG 欄目:移動(dòng)開發(fā)

Android熱修復(fù)原理分析

本文目的是讓大家了解什么是熱修復(fù),具體的實(shí)現(xiàn)細(xì)節(jié)可以聯(lián)系我。大家了解了原理后,才能夠順利的帶著手寫代碼了。請大家記住一句話:
紙上得來終覺淺,絕知此事要躬行。
代碼需要大家自己寫出來的那才是自己的代碼,否則的話你看了,背了,最后工作中還是寫不出來優(yōu)秀的代碼

什么是熱修復(fù)

熱修復(fù): 讓應(yīng)用能夠在無需重新安裝的情況實(shí)現(xiàn)更新,幫助應(yīng)用快速建立動(dòng)態(tài)修復(fù)能力。

早期遇到Bug我們一般會(huì)緊急發(fā)布了一個(gè)版本。然而這個(gè)Bug可能就是簡簡單單的一行代碼,為了這一行代碼,進(jìn)行全量或者增量更新迭代一個(gè)版本,未免有點(diǎn)大材小用了。而且新版本的普及需要時(shí)間,以Android用戶的升級習(xí)慣,即使是相對活躍的微信也需要10天以上的時(shí)間去覆蓋50%的用戶。使用熱修復(fù)技術(shù),能做到1天覆蓋70%以上。這也是基于補(bǔ)丁體積較小,可以直接使用移動(dòng)網(wǎng)絡(luò)下載更新。

熱修復(fù)開發(fā)流程
還不懂熱修復(fù)的原理?看完這篇后帶你手把手寫代碼

目前Android業(yè)內(nèi),熱修復(fù)技術(shù)百花齊放,各大廠都推出了自己的熱修復(fù)方案,使用的技術(shù)方案也各有所異。其中QZone超級補(bǔ)丁基于的是dex分包方案,而dex分包是基于Java的類加載機(jī)制 ClassLoader

ClassLoader介紹

任何一個(gè) Java 程序都是由一個(gè)或多個(gè) class 文件組成,在程序運(yùn)行時(shí),需要將 class 文件加載到虛擬機(jī) 中才可以使用,負(fù)責(zé)加載這些 class 文件的就是 Java 的類加載機(jī)制。 ClassLoader 的作用簡單來說就是加載 class 文件,提供給程序運(yùn)行時(shí)使用。每個(gè) Class 對象的內(nèi)部都有一個(gè) classLoader字段來標(biāo)識(shí)自己是由哪個(gè) ClassLoader加載的。

class Class<T> {
  ...
  private transient ClassLoader classLoader;
  ...
}

ClassLoader是一個(gè)抽象類,而它的主要實(shí)現(xiàn)類主要有:

  • BootClassLoader
    用于加載Android Framework層class文件
  • PathClassLoader
    用于Android應(yīng)用程序類加載器。可以加載指定的dex,以及jar、zip、apk中的classes.dex
  • DexClassLoader
    用于加載指定的dex,以及jar、zip、apk中的classes.dex

    很多博客里說 PathClassLoader只能加載已安裝的apkdex,但是實(shí)際上 PathClassLoaderDexClassLoader一樣都能夠加載sdcard中的dex。

Log.e(TAG, "Activity.class 由:" + Activity.class.getClassLoader() +" 加載");
Log.e(TAG, "MainActivity.class 由:" + MainActivity.class.getClassLoader() +" 加載");

//輸出:
Activity.class 由:java.lang.BootClassLoader@d3052a9 加載
MainActivity.class 由:dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/com.enjoy.enjoyfix-1/base.apk"],nativeLibraryDirectories[/data/app/com.enjoy.enoyfix-1/lib/x86, /system/lib, /vendor/lib]]] 加載

它們之間的關(guān)系如下:
還不懂熱修復(fù)的原理?看完這篇后帶你手把手寫代碼
PathClassLoaderDexClassLoader的共同父類是 BaseDexClassLoader

public class DexClassLoader extends BaseDexClassLoader {
   public DexClassLoader(String dexPath,String optimizedDirectory,
        String librarySearchPath, ClassLoader parent) {
        super(dexPath, new File(optimizedDirectory),librarySearchPath, parent);
    }
}
public class PathClassLoader extends BaseDexClassLoader {
     public PathClassLoader(String dexPath, ClassLoader parent) {
        super(dexPath, null, null, parent);
    }

    public PathClassLoader(String dexPath, String librarySearchPath, ClassLoader parent){
         super(dexPath, null, librarySearchPath, parent);
    }
}

可以看到兩者唯一的區(qū)別在于: 創(chuàng)建 DexClassLoader需要傳遞一個(gè) optimizedDirectory參數(shù),并且會(huì)將其創(chuàng)建為 File對象傳給 super,而 PathClassLoader則直接給到null。因此兩者都可以加載指定的dex,以及jar、zip、apk中的classes.dex

PathClassLoader pathClassLoader = new PathClassLoader("/sdcard/xx.dex", getClassLoader());

File dexOutputDir = context.getCodeCacheDir();
DexClassLoader dexClassLoader= new DexClassLoader("/sdcard/xx.dex",dexOutputDir.getAbsolutePath(), null,getClassLoader());

optimizedDirectory參數(shù)為odex的目錄。實(shí)際上Android中的ClassLoader在加載dex時(shí),會(huì)首先經(jīng)過dexoptdex執(zhí)行優(yōu)化,產(chǎn)生odex文件。optimizedDirectory為null時(shí)的默認(rèn)路徑為:/data/dalvik-cache。并且處于安全考慮,此目錄需要使用app私有目錄,如:getCodeCacheDir()

在API 26源碼中,將DexClassLoader的optimizedDirectory標(biāo)記為了 deprecated 棄用,實(shí)現(xiàn)也變?yōu)榱耍?/p>

javapublicDexClassLoader(StringdexPath,StringoptimizedDirectory,StringlibrarySearchPath,ClassLoaderparent){super(dexPath,null,librarySearchPath,parent);}

PathClassLoader一摸一樣了!

雙親委托機(jī)制

創(chuàng)建 ClassLoader需要接收一個(gè) ClassLoaderparent參數(shù)。這個(gè) parent為父類加載。即:某個(gè)類加載器在接到加載類的請求時(shí),首先將加載任務(wù)委托給父類加載器,依次遞歸,如果父類加載器可以完成類加載任務(wù),就成功返回;只有父類加載器無法完成此加載任務(wù)時(shí),才自己去加載。這就是雙親委托機(jī)制!

protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException{

    // 檢查class是否有被加載
    Class c = findLoadedClass(name);
    if (c == null) {
        long t0 = System.nanoTime();
        try {
            if (parent != null) {
                //如果parent不為null,則調(diào)用parent的loadClass行加載
                c = parent.loadClass(name, false);
            } else {
                //parent為null,則調(diào)用BootClassLoader進(jìn)行加載
                c = findBootstrapClassOrNull(name);
            }
        } catch (ClassNotFoundException e) {

        }

        if (c == null) {
            // 如果都找不到就自己查找
            long t1 = System.nanoTime();
            c = findClass(name);
        }
    }
    return c;
}

因此我們自己創(chuàng)建的ClassLoader: newPathClassLoader("/sdcard/xx.dex",getClassLoader());并不僅僅只能獲得 xx.dex中的Class,還能夠獲得其父ClassLoader中加載的Class。

findClass

在所有父ClassLoader無法加載Class時(shí),則會(huì)調(diào)用自己的 findClass方法。findClassClassLoader中的定義為:

protected Class<?> findClass(String name) throws ClassNotFoundException {
    throw new ClassNotFoundException(name);
}

其實(shí)任何ClassLoader子類,都可以重寫 loadClassfindClass。一般如果你不想使用雙親委托,則重寫 loadClass修改其實(shí)現(xiàn)。而重寫 findClass則表示在雙親委托下,父ClassLoader都找不到Class的情況下,定義自己如何去查找一個(gè)Class。而我們的 PathClassLoader會(huì)自己負(fù)責(zé)加載 MainActivity這樣的程序中自己編寫的類,利用雙親委托父ClassLoader加載Framework中的 Activity。說明 PathClassLoader并沒有重寫 loadClass,因此我們可以來看看PathClassLoader中的 findClass 是如何實(shí)現(xiàn)的。

public BaseDexClassLoader(String dexPath, FileoptimizedDirectory,String
                        librarySearchPath, ClassLoader parent) {
    super(parent);
    this.pathList = new DexPathList(this, dexPath, librarySearchPath,
                                    optimizedDirectory);
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
    List<Throwable> suppressedExceptions = new ArrayList<Throwable>();
    //查找指定的class
    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;
}

實(shí)現(xiàn)非常簡單,從pathList中查找class。繼續(xù)查看 DexPathList

public DexPathList(ClassLoader definingContext, String dexPath,
            String librarySearchPath, File optimizedDirectory) {
    //.........
    // splitDexPath 實(shí)現(xiàn)為返回 List<File>.add(dexPath)
    // makeDexElements 會(huì)去 List<File>.add(dexPath) 中使用DexFile加載dex文件返回 Element數(shù)組

this.dexElements =makeDexElements(splitDexPath(dexPath), optimizedDirectory,
                                           suppressedExceptions, definingContext);
    //.........
}
public Class findClass(String name, List<Throwable> suppressed) {
     //從element中獲得代表Dex的 DexFile
    for (Element element : dexElements) {
        DexFile dex = element.dexFile;
        if (dex != null) {
            //查找class
            Class clazz = dex.loadClassBinaryName(name,definingContext, suppressed);
            if (clazz != null) {
                return clazz;
            }
        }
    }
    if (dexElementsSuppressedExceptions != null) {
        suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
    }
    return null;
}

熱修復(fù)

PathClassLoader中存在一個(gè)Element數(shù)組,Element類中存在一個(gè) dexFile成員表示dex文件,即:APK中有X個(gè)dex,則Element數(shù)組就有X個(gè)元素。
還不懂熱修復(fù)的原理?看完這篇后帶你手把手寫代碼
而對于類的查找,由代碼for(Elementelement:dexElements)得知,會(huì)由數(shù)組從前往后進(jìn)行查找。
還不懂熱修復(fù)的原理?看完這篇后帶你手把手寫代碼

PathClassLoader中的Element數(shù)組為:[patch.dex , classes.dex , classes2.dex]。如果存在Key.class位于patch.dexclasses2.dex中都存在一份,當(dāng)進(jìn)行類查找時(shí),循環(huán)獲得 dexElements中的 DexFile,查找到了Key.class則立即返回,不會(huì)再管后續(xù)的element中的 DexFile是否能加載到Key.class了。

因此,可以將出現(xiàn)Bug的class單獨(dú)的制作一份patch.dex文件(補(bǔ)丁包),然后在程序啟動(dòng)時(shí),從服務(wù)器下載patch.dex保存到某個(gè)路徑,再通過patch.dex的文件路徑,用其創(chuàng)建 Element對象,然后將這個(gè) Element對象插入到我們程序的類加載器 PathClassLoaderpathList中的 dexElements數(shù)組頭部。這樣在加載出現(xiàn)Bug的class時(shí)會(huì)優(yōu)先加載patch.dex中的修復(fù)類,從而解決Bug。QQ空間熱修復(fù)的原理就是這樣,利用反射Hook了PathClassLoaderpathListdexElements數(shù)組。

總結(jié)

在實(shí)現(xiàn)熱修復(fù)的過程中,必須掌握的技術(shù)包括了反射、類加載機(jī)制并且掌握Framewrok層源碼中關(guān)于 ClassLoader部分的內(nèi)容。同時(shí)如果需要繼續(xù)自動(dòng)化補(bǔ)丁生成還需要掌握gradle編程等內(nèi)容。

文章中每一個(gè)部分都包含一系列BAT面試的面試點(diǎn),這些點(diǎn)構(gòu)建了一個(gè)完整的知識(shí)體系,熱修復(fù)。后面,我會(huì)細(xì)化里面的知識(shí),如果 大家覺得有問題,可以和我交流~

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI