Android類(lèi)加載機(jī)制的細(xì)枝末節(jié)

最近在搞android插件化贮配,其中最核心的一點(diǎn)就是如何讓插件apk跑起來(lái)谍倦,其實(shí)就利用動(dòng)態(tài)加載,將插件中的類(lèi)加載到host中泪勒。之前也看過(guò)android類(lèi)的加載機(jī)制昼蛀,感覺(jué)對(duì)于整個(gè)過(guò)程大致看懂了,然后就開(kāi)始整插件化圆存,結(jié)果碰到問(wèn)題了又感覺(jué)一知半解的叼旋,很是不爽,索性放一放插件化沦辙,先把a(bǔ)ndroid的類(lèi)加載機(jī)制看個(gè)明白夫植。這里給自己總結(jié)下,加深印象的同時(shí)保留筆記油讯。

首先需要知道的是android中兩個(gè)主要的classloader详民,PathClassLoader和DexClassLoader,它們都繼承自BaseDexClassLoader陌兑,這兩個(gè)類(lèi)有什么區(qū)別呢沈跨?其實(shí)看一下它們的源碼注釋就一目了然了。
因?yàn)榇a很少兔综,約等于沒(méi)有饿凛,這里直接貼出它們的源碼,其實(shí)主要是注釋?zhuān)?/p>

PathClassLoader

package dalvik.system;
/**
 * Provides a simple {@link ClassLoader} implementation that operates on a list
 * of files and directories in the local file system, but does not attempt to
 * load classes from the network. Android uses this class for its system class
 * loader and for its application class loader(s).
 */
public class PathClassLoader extends BaseDexClassLoader {
    /**
     * Creates a {@code PathClassLoader} that operates on a given list of files
     * and directories. This method is equivalent to calling
     * {@link #PathClassLoader(String, String, ClassLoader)} with a
     * {@code null} value for the second argument (see description there).
     *
     * @param dexPath the list of jar/apk files containing classes and
     * resources, delimited by {@code File.pathSeparator}, which
     * defaults to {@code ":"} on Android
     * @param parent the parent class loader
     */
    public PathClassLoader(String dexPath, ClassLoader parent) {
        super(dexPath, null, null, parent);
    }
    /**
     * Creates a {@code PathClassLoader} that operates on two given
     * lists of files and directories. The entries of the first list
     * should be one of the following:
     *
     * <ul>
     * <li>JAR/ZIP/APK files, possibly containing a "classes.dex" file as
     * well as arbitrary resources.
     * <li>Raw ".dex" files (not inside a zip file).
     * </ul>
     *
     * The entries of the second list should be directories containing
     * native library files.
     *
     * @param dexPath the list of jar/apk files containing classes and
     * resources, delimited by {@code File.pathSeparator}, which
     * defaults to {@code ":"} on Android
     * @param libraryPath the list of directories containing native
     * libraries, delimited by {@code File.pathSeparator}; may be
     * {@code null}
     * @param parent the parent class loader
     */
    public PathClassLoader(String dexPath, String libraryPath,
            ClassLoader parent) {
        super(dexPath, null, libraryPath, parent);
    }
}

由注釋看可以發(fā)現(xiàn)PathClassLoader被用來(lái)加載本地文件系統(tǒng)上的文件或目錄软驰,但不能從網(wǎng)絡(luò)上加載涧窒,關(guān)鍵是它被用來(lái)加載系統(tǒng)類(lèi)和我們的應(yīng)用程序,這也是為什么它的兩個(gè)構(gòu)造函數(shù)中調(diào)用父類(lèi)構(gòu)造器的時(shí)候第二個(gè)參數(shù)傳null锭亏,具體的參數(shù)意義請(qǐng)看接下來(lái)DexClassLoader的注釋纠吴。

DexClassLoader

package dalvik.system;
import java.io.File;
/**
 * A class loader that loads classes from {@code .jar} and {@code .apk} files
 * containing a {@code classes.dex} entry. This can be used to execute code not
 * installed as part of an application.
 *
 * <p>This class loader requires an application-private, writable directory to
 * cache optimized classes. Use {@code Context.getDir(String, int)} to create
 * such a directory: <pre>   {@code
 *   File dexOutputDir = context.getDir("dex", 0);
 * }</pre>
 *
 * <p><strong>Do not cache optimized classes on external storage.</strong>
 * External storage does not provide access controls necessary to protect your
 * application from code injection attacks.
 */
public class DexClassLoader extends BaseDexClassLoader {
    /**
     * Creates a {@code DexClassLoader} that finds interpreted and native
     * code.  Interpreted classes are found in a set of DEX files contained
     * in Jar or APK files.
     *
     * <p>The path lists are separated using the character specified by the
     * {@code path.separator} system property, which defaults to {@code :}.
     *
     * @param dexPath the list of jar/apk files containing classes and
     *     resources, delimited by {@code File.pathSeparator}, which
     *     defaults to {@code ":"} on Android
     * @param optimizedDirectory directory where optimized dex files
     *     should be written; must not be {@code null}
     * @param libraryPath the list of directories containing native
     *     libraries, delimited by {@code File.pathSeparator}; may be
     *     {@code null}
     * @param parent the parent class loader
     */
    public DexClassLoader(String dexPath, String optimizedDirectory,
            String libraryPath, ClassLoader parent) {
        super(dexPath, new File(optimizedDirectory), libraryPath, parent);
    }
}

DexClassLoader用來(lái)加載jar、apk慧瘤,其實(shí)還包括zip文件或者直接加載dex文件呜象,它可以被用來(lái)執(zhí)行未安裝的代碼或者未被應(yīng)用加載過(guò)的代碼膳凝。這里也寫(xiě)出了它需要的四個(gè)參數(shù)的意思

  1. dexPath:需要被加載的文件地址,可以多個(gè)恭陡,用File.pathSeparator分割
  2. optimizedDirectory:dex文件被加載后會(huì)被編譯器優(yōu)化蹬音,優(yōu)化之后的dex存放路徑,不可以為null休玩。注意著淆,注釋中也提到需要一個(gè)應(yīng)用私有的可寫(xiě)的一個(gè)路徑,以防止應(yīng)用被注入攻擊拴疤,并且給出了例子 File dexOutputDir = context.getDir("dex", 0);
  3. libraryPath:包含libraries的目錄列表永部,plugin中有so文件,需要將so拷貝到sd卡上呐矾,然后把so所在的目錄當(dāng)參數(shù)傳入苔埋,同樣用File.pathSeparator分割,如果沒(méi)有則傳null就行了,會(huì)自動(dòng)加上系統(tǒng)so庫(kù)的存放目錄
  4. parent:父類(lèi)構(gòu)造器

這里著重看一下第二個(gè)參數(shù)蜒犯,之前說(shuō)過(guò)PathClassLoader中調(diào)用父類(lèi)構(gòu)造器的時(shí)候這個(gè)參數(shù)穿了null组橄,因?yàn)榧虞dapp應(yīng)用的時(shí)候我們的apk已經(jīng)被安裝到本地文件系統(tǒng)上了,其內(nèi)部的dex已經(jīng)被提取并且執(zhí)行過(guò)優(yōu)化了罚随,優(yōu)化之后放在系統(tǒng)目錄/data/dalvik-cache下玉工。

接下來(lái)我們看一下類(lèi)的具體加載過(guò)程

首先看一段如何使用類(lèi)加載器加載的調(diào)用代碼:

    try {
            File file = view.getActivity().getDir("dex",0);
            String optimizedDirectory = file.getAbsolutePath();
            DexClassLoader loader = new DexClassLoader("需要被加載的dex文件所在的路徑",optimizedDirectory,null,context.getClassLoader());
            loader.loadClass("需要加載的類(lèi)的完全限定名");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

這里我們就用了自定義了一個(gè)DexClassLoaderLoader,并且調(diào)用了它的loadClass方法淘菩,這樣一個(gè)需要被使用的類(lèi)就被我們加載進(jìn)來(lái)了遵班,接下去就可以正常使用這個(gè)類(lèi)了,具體怎么使用我就不多說(shuō)了潮改,我們還是來(lái)研究研究這個(gè)類(lèi)是怎么被加載進(jìn)來(lái)的吧~

可以看到new DexClassLoader的時(shí)候我們用了4個(gè)參數(shù),參數(shù)意義上面已經(jīng)講過(guò)了翰萨,從上面的源碼中可以看到DexClassLoader的構(gòu)造器中直接調(diào)用了父類(lèi)的構(gòu)造器趾疚,只是將optimizedDirectory路徑封裝成一個(gè)File以蕴,具體這些參數(shù)是如何被使用的呢,我們往下看丛肮。

BaseDexClassLoader類(lèi)的構(gòu)造器

    /**
     * Constructs an instance.
     *
     * @param dexPath the list of jar/apk files containing classes and
     * resources, delimited by {@code File.pathSeparator}, which
     * defaults to {@code ":"} on Android
     * @param optimizedDirectory directory where optimized dex files
     * should be written; may be {@code null}
     * @param libraryPath the list of directories containing native
     * libraries, delimited by {@code File.pathSeparator}; may be
     * {@code null}
     * @param parent the parent class loader
     */
  public BaseDexClassLoader(String dexPath, File optimizedDirectory,
            String libraryPath, ClassLoader parent) {
        super(parent);
        this.originalPath = dexPath;
        this.pathList =
            new DexPathList(this, dexPath, libraryPath, optimizedDirectory);
    }

首先也是調(diào)用了父類(lèi)的構(gòu)造器,但這里只將parent傳給父類(lèi)宝与,即ClassLoader焚廊,ClassLoader中做的也很很簡(jiǎn)單冶匹,它內(nèi)部有個(gè)parent屬性,正好保存?zhèn)鬟M(jìn)來(lái)的參數(shù)parent,這里可以稍微看一下第二個(gè)參數(shù)的注釋?zhuān)詈笠痪湔f(shuō)到可以為null咆瘟,而是否為null又剛好是PathClassLoader和DexClassLoader的區(qū)別嚼隘,那是否為null最終又意味著什么呢?賣(mài)個(gè)關(guān)子先~

    protected ClassLoader(ClassLoader parentLoader) {
        this(parentLoader, false);
    }

    ClassLoader(ClassLoader parentLoader, boolean nullAllowed) {
        if (parentLoader == null && !nullAllowed) {
            throw new NullPointerException("parentLoader == null && !nullAllowed");
        }
        parent = parentLoader;
    }

接下去BaseDexClassLoader給originalPath 和 pathList賦了值袒餐,originalPath就是我們傳進(jìn)入的dex文件路徑飞蛹,pathList 是一個(gè)new 出來(lái)的DexPathList對(duì)象,這個(gè)DexPathList又是何方神圣灸眼?且聽(tīng)慢慢道來(lái)卧檐。

 /**
     * Constructs an instance.
     *
     * @param definingContext the context in which any as-yet unresolved
     * classes should be defined
     * @param dexPath list of dex/resource path elements, separated by
     * {@code File.pathSeparator}
     * @param libraryPath list of native library directory path elements,
     * separated by {@code File.pathSeparator}
     * @param optimizedDirectory directory where optimized {@code .dex} files
     * should be found and written to, or {@code null} to use the default
     * system directory for same
     */
    public DexPathList(ClassLoader definingContext, String dexPath,
            String libraryPath, File optimizedDirectory) {
        if (definingContext == null) {
            throw new NullPointerException("definingContext == null");
        }
        if (dexPath == null) {
            throw new NullPointerException("dexPath == null");
        }
        if (optimizedDirectory != null) {
            if (!optimizedDirectory.exists())  {
                throw new IllegalArgumentException(
                        "optimizedDirectory doesn't exist: "
                        + optimizedDirectory);
            }
            if (!(optimizedDirectory.canRead()
                            && optimizedDirectory.canWrite())) {
                throw new IllegalArgumentException(
                        "optimizedDirectory not readable/writable: "
                        + optimizedDirectory);
            }
        }
        this.definingContext = definingContext;
        this.dexElements =
            makeDexElements(splitDexPath(dexPath), optimizedDirectory);
        this.nativeLibraryDirectories = splitLibraryPath(libraryPath);
    }

別的先不說(shuō),先看注釋焰宣。第四個(gè)參數(shù)中說(shuō)到如果optimizedDirectory 為null則使用系統(tǒng)默認(rèn)路徑代替霉囚,這個(gè)默認(rèn)路徑也就是/data/dalvik-cache/目錄,這個(gè)一般情況下是沒(méi)有權(quán)限去訪問(wèn)的匕积,所以這也就解釋了為什么我們只能用DexClassLoader去加載類(lèi)而不用PathClassLoader盈罐。

然后接著看代碼,顯然闸天,前面三個(gè)if判斷都是用來(lái)驗(yàn)證參數(shù)的合法性的暖呕,之后同樣只是做了三個(gè)賦值操作,第一個(gè)就不說(shuō)了苞氮,保存了實(shí)例化DexPathList的classloader湾揽,第二個(gè)參數(shù)的聲明是一個(gè)Element數(shù)組,第三個(gè)參數(shù)是lib庫(kù)的目錄文件數(shù)組,

private final Element[] dexElements;
private final File[] nativeLibraryDirectories;

看它們之前先看看幾個(gè)split小函數(shù):

private static ArrayList<File> splitDexPath(String path) {
    return splitPaths(path, null, false);
}

private static File[] splitLibraryPath(String path) {
    ArrayList<File> result = splitPaths(
            path, System.getProperty("java.library.path", "."), true);
    return result.toArray(new File[result.size()]);
} 

這兩個(gè)顧名思義就是拿來(lái)分割dexPath和libPath笼吟,它們內(nèi)部都調(diào)用了splitPaths方法库物,只是三個(gè)參數(shù)不一樣,其中splitLibraryPath方法中調(diào)用splitPaths時(shí)的第二個(gè)參數(shù)仿佛又透露了什么信息贷帮,沒(méi)錯(cuò),之前介紹DexClassLoader參數(shù)中的libraryPath的時(shí)候說(shuō)過(guò)民晒,會(huì)加上系統(tǒng)so庫(kù)的存放目錄潜必,就是在這個(gè)時(shí)候添加上去的磁滚。

    private static ArrayList<File> splitPaths(String path1, String path2,
            boolean wantDirectories) {
        ArrayList<File> result = new ArrayList<File>();
        splitAndAdd(path1, wantDirectories, result);
        splitAndAdd(path2, wantDirectories, result);
        return result;
    }

什么啊维雇,原來(lái)這個(gè)方法也沒(méi)做什么事啊吱型,只是把參數(shù)path1和參數(shù)path2又分別調(diào)用了下splitAndAdd方法唁影,但是這里創(chuàng)建了一個(gè)ArrayList,而且調(diào)用splitAndAdd方法的時(shí)候都當(dāng)參數(shù)傳入了掂名,并且最終返回了這個(gè)list饺蔑,所以我們大膽猜測(cè)下猾警,path1和path2最后被分割后的值都存放在了list中返回,看下是不是這么一回事吧:

    private static void splitAndAdd(String path, boolean wantDirectories,
            ArrayList<File> resultList) {
        if (path == null) {
            return;
        }
        String[] strings = path.split(Pattern.quote(File.pathSeparator));
        for (String s : strings) {
            File file = new File(s);
            if (! (file.exists() && file.canRead())) {
                continue;
            }
            /*
             * Note: There are other entities in filesystems than
             * regular files and directories.
             */
            if (wantDirectories) {
                if (!file.isDirectory()) {
                    continue;
                }
            } else {
                if (!file.isFile()) {
                    continue;
                }
            }
            resultList.add(file);
        }
    }

果然崔慧,跟我們猜的一樣穴墅,只是又加上了文件是否存在以及是否可讀的驗(yàn)證玄货,然后根據(jù)參數(shù)wantDirectories判斷是否文件類(lèi)型是被需要的類(lèi)型松捉,最終加入list“溃現(xiàn)在我們回過(guò)頭去看看splitDexPath方法和splitLibraryPath方法铃剔,是不是一目了然了玛荞。

再往上看DexPathList的構(gòu)造器,nativeLibraryDirectories的最終值也已經(jīng)知道了滥沫,就差dexElements了,makeDexElements方法的兩個(gè)參數(shù)我們也已經(jīng)知道了世分,那我們就看看makeDexElements都干了些什么吧臭埋。

   private static Element[] makeDexElements(ArrayList<File> files,
           File optimizedDirectory) {
       ArrayList<Element> elements = new ArrayList<Element>();
       /*
        * Open all files and load the (direct or contained) dex files
        * up front.
        */
       for (File file : files) {
           ZipFile zip = null;
           DexFile dex = null;
           String name = file.getName();
           if (name.endsWith(DEX_SUFFIX)) {
               // Raw dex file (not inside a zip/jar).
               try {
                   dex = loadDexFile(file, optimizedDirectory);
               } catch (IOException ex) {
                   System.logE("Unable to load dex file: " + file, ex);
               }
           } else if (name.endsWith(APK_SUFFIX) || name.endsWith(JAR_SUFFIX)
                   || name.endsWith(ZIP_SUFFIX)) {
               try {
                   zip = new ZipFile(file);
               } catch (IOException ex) {
                   /*
                    * Note: ZipException (a subclass of IOException)
                    * might get thrown by the ZipFile constructor
                    * (e.g. if the file isn't actually a zip/jar
                    * file).
                    */
                   System.logE("Unable to open zip file: " + file, ex);
               }
               try {
                   dex = loadDexFile(file, optimizedDirectory);
               } catch (IOException ignored) {
                   /*
                    * IOException might get thrown "legitimately" by
                    * the DexFile constructor if the zip file turns
                    * out to be resource-only (that is, no
                    * classes.dex file in it). Safe to just ignore
                    * the exception here, and let dex == null.
                    */
               }
           } else {
               System.logW("Unknown file type for: " + file);
           }
           if ((zip != null) || (dex != null)) {
               elements.add(new Element(file, zip, dex));
           }
       }
       return elements.toArray(new Element[elements.size()]);
   }

方法也不長(zhǎng),我們一段段看下去荣恐。首先創(chuàng)建了一個(gè)elememt 列表叠穆,然后遍歷由dexpath分割得來(lái)的文件列表硼被,其實(shí)一般使用場(chǎng)景下也就一個(gè)文件嚷硫。循環(huán)里面針對(duì)每個(gè)file 聲明一個(gè)zipfile和一個(gè)dexfile论巍,判斷file的文件后綴名嘉汰,如果是".dex"則使用loadDexFile方法給dex賦值鞋怀,如果是“.apk”,“.jar”,“.zip”文件的則把file包裝成zipfile賦值給zip密似,然后同樣是用loadDexFile方法給dex賦值残腌,如果是其他情況則不做處理抛猫,打印日志說(shuō)明文件類(lèi)型不支持闺金,而且下一個(gè)if判斷中由于zip和dex都未曾賦值败匹,所以也不會(huì)添加到elements列表中去掀亩。注意下:這里所謂的文件類(lèi)型僅僅是指文件的后綴名而已槽棍,并不是文件的實(shí)際類(lèi)型刹泄,比如我們將.zip文件后綴改成.txt特石,那么就不支持這個(gè)文件了姆蘸。而且我們可以看到對(duì)于dexpath目前只支持“.dex”逞敷、“.jar”推捐、“.apk”牛柒、“.zip”這四種類(lèi)型皮壁。

現(xiàn)在還剩下兩個(gè)東西可能還不太明確蛾魄,一個(gè)是什么是DexFile以及這里的loadDexFile方法是如何創(chuàng)建dexfile實(shí)例的湿滓,另一個(gè)是什么是Elememt叽奥,看了下Element源碼,哈哈恋日,so easy岂膳,就是一個(gè)簡(jiǎn)單的數(shù)據(jù)結(jié)構(gòu)體加一個(gè)方法磅网,所以我們就先簡(jiǎn)單把它當(dāng)做一個(gè)存儲(chǔ)了file簸喂,zip喻鳄,dex三個(gè)字段的一個(gè)實(shí)體類(lèi)除呵。那么就剩下DexFile了颜曾。

   private static DexFile loadDexFile(File file, File optimizedDirectory)
           throws IOException {
       if (optimizedDirectory == null) {
           return new DexFile(file);
       } else {
           String optimizedPath = optimizedPathFor(file, optimizedDirectory);
           return DexFile.loadDex(file.getPath(), optimizedPath, 0);
       }
   }
   
   

很簡(jiǎn)潔泛豪,如果optimizedDirectory == null則直接new 一個(gè)DexFile候址,否則就使用DexFile.loadDex來(lái)創(chuàng)建一個(gè)DexFile實(shí)例岗仑。

/**
    * Converts a dex/jar file path and an output directory to an
    * output file path for an associated optimized dex file.
    */
   private static String optimizedPathFor(File path,
           File optimizedDirectory) {
       /*
        * Get the filename component of the path, and replace the
        * suffix with ".dex" if that's not already the suffix.
        *
        * We don't want to use ".odex", because the build system uses
        * that for files that are paired with resource-only jar
        * files. If the VM can assume that there's no classes.dex in
        * the matching jar, it doesn't need to open the jar to check
        * for updated dependencies, providing a slight performance
        * boost at startup. The use of ".dex" here matches the use on
        * files in /data/dalvik-cache.
        */
       String fileName = path.getName();
       if (!fileName.endsWith(DEX_SUFFIX)) {
           int lastDot = fileName.lastIndexOf(".");
           if (lastDot < 0) {
               fileName += DEX_SUFFIX;
           } else {
               StringBuilder sb = new StringBuilder(lastDot + 4);
               sb.append(fileName, 0, lastDot);
               sb.append(DEX_SUFFIX);
               fileName = sb.toString();
           }
       }
       File result = new File(optimizedDirectory, fileName);
       return result.getPath();
   }

這個(gè)方法獲取被加載的dexpath的文件名稳其,如果不是“.dex”結(jié)尾的就改成“.dex”結(jié)尾既鞠,然后用optimizedDirectory和新的文件名構(gòu)造一個(gè)File并返回該File的路徑嘱蛋,所以DexFile.loadDex方法的第二個(gè)參數(shù)其實(shí)是dexpath文件對(duì)應(yīng)的優(yōu)化文件的輸出路徑洒敏。比如我要加載一個(gè)dexpath為“sdcard/coder_yu/plugin.apk”凶伙,optimizedDirectory 為使用范例中的目錄的話函荣,那么最終優(yōu)化后的輸出路徑為/data/user/0/com.coder_yu.test/app_dex/plugin.dex,具體的目錄在不同機(jī)型不同rom下有可能會(huì)不一樣傻挂。

是時(shí)候看看DexFile了踊谋。在上面的loadDexFile方法中我們看到optimizedDirectory參數(shù)為null的時(shí)候直接返回new DexFile(file)了殖蚕,否則返回 DexFile.loadDex(file.getPath(), optimizedPath, 0)睦疫,但其實(shí)他們最終都是使用了相同方法去加載dexpath文件蛤育,因?yàn)镈exFile.loadDex方法內(nèi)部也是直接調(diào)用的了DexFile的構(gòu)造器瓦糕,以下:

   static public DexFile loadDex(String sourcePathName, String outputPathName,
       int flags) throws IOException {
       return new DexFile(sourcePathName, outputPathName, flags);
   }

然后看看DexFile的構(gòu)造器吧


   public DexFile(File file) throws IOException {
       this(file.getPath());
   }
   /**
    * Opens a DEX file from a given filename. This will usually be a ZIP/JAR
    * file with a "classes.dex" inside.
    *
    * The VM will generate the name of the corresponding file in
    * /data/dalvik-cache and open it, possibly creating or updating
    * it first if system permissions allow.  Don't pass in the name of
    * a file in /data/dalvik-cache, as the named file is expected to be
    * in its original (pre-dexopt) state.
    *
    * @param fileName
    *            the filename of the DEX file
    *
    * @throws IOException
    *             if an I/O error occurs, such as the file not being found or
    *             access rights missing for opening it
    */
   public DexFile(String fileName) throws IOException {
       mCookie = openDexFile(fileName, null, 0);
       mFileName = fileName;
       guard.open("close");
       //System.out.println("DEX FILE cookie is " + mCookie);
   }
   
   private DexFile(String sourceName, String outputName, int flags) throws IOException {
       mCookie = openDexFile(sourceName, outputName, flags);
       mFileName = sourceName;
       guard.open("close");
       //System.out.println("DEX FILE cookie is " + mCookie);
   }

可以看到直接new DexFile(file)和DexFile.loadDex(file.getPath(), optimizedPath, 0)最終都是調(diào)用了openDexFile(sourceName咕娄, outputName, flags)方法费变,只是直接new的方式optimizedPath參數(shù)為null圣贸,這樣openDexFile方法會(huì)默認(rèn)使用 /data/dalvik-cache目錄作為優(yōu)化后的輸出目錄吁峻,第二個(gè)構(gòu)造器的注釋中寫(xiě)的很明白了矮慕。mCookie是一個(gè)int值耕餐,保存了openDexFile方法的返回值肠缔,openDexFile方法是一個(gè)native方法明未,我們就不深入了趟妥,我自己也就看了個(gè)大概披摄,有興趣的同學(xué)可以看下這篇文章:
http://blog.csdn.net/zhoushishang/article/details/38703623
這樣總算是創(chuàng)建了一個(gè)DexClassLoader疚膊。

我們回顧一下:從new DexClassLoader(dexPath,optimizedDirectory,libraryPath,parentLoader)開(kāi)始,調(diào)用父類(lèi)BaseDexClassLoader構(gòu)造器寓盗,用originalPath 保存了 dexPath傀蚌,pathList保存了一個(gè)由dexPath善炫、optimizedDirectory销部、libraryPath、loader四個(gè)參數(shù)構(gòu)建的DexPathList酱虎,DexPathList中的definingContext 保存了parentLoader读串,optimizedDirectory和libraryPath會(huì)被分割成數(shù)組恢暖,其中nativeLibraryDirectories保存了libraryPath被分割后的數(shù)組杰捂,并且加上了系統(tǒng)so庫(kù)的目錄嫁佳,dexElements保存了由dexPath被分割后的對(duì)應(yīng)的file而創(chuàng)建的Elememt蒿往,它只是一個(gè)簡(jiǎn)單實(shí)體類(lèi)瓤漏,由一個(gè)File蔬充,一個(gè)ZipFile饥漫,一個(gè)DexFile組成趾浅,ZipFile是由jar皿哨、zip证膨、apk形式的file包裝成而來(lái)不见,DexFile使用native方法openDexFile打開(kāi)了具體的file并輸出到優(yōu)化路徑稳吮。
classloader 關(guān)系圖.jpg

DexClassLoader的創(chuàng)建過(guò)程我們已經(jīng)看完了灶似,看看它是如何去找到我們需要的類(lèi)的吧酪惭!

先把使用范例再貼一遍

    try {
            File file = view.getActivity().getDir("dex",0);
            String optimizedDirectory = file.getAbsolutePath();
            DexClassLoader loader = new DexClassLoader("需要被加載的dex文件所在的路徑",optimizedDirectory,null,context.getClassLoader());
            loader.loadClass("需要加載的類(lèi)的完全限定名");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

我們已經(jīng)拿到loader了春感,然后就是loader.loadClass("需要加載的類(lèi)的完全限定名"),我們假設(shè)需要被加載的類(lèi)為 'com.coder_yu.plugin.Test',也即是loader.loadClass("com.coder_yu.plugin.Test"),所以我們先去DexClassLoader中看看loaderClass方法刀疙,不過(guò)好像沒(méi)有這個(gè)方法扫倡,那就去它的父類(lèi)BaseDexClassLoader中看看撵溃,還是 沒(méi)有,那就再往上走锥累,去ClassLoader類(lèi)里面找缘挑,總算找到了:

 public Class<?> loadClass(String className) throws ClassNotFoundException {
    return loadClass(className, false);
}

直接調(diào)用另一個(gè)loadclass方法,看一下:

    protected Class<?> loadClass(String className, boolean resolve) throws ClassNotFoundException {
        Class<?> clazz = findLoadedClass(className);

        if (clazz == null) {
            ClassNotFoundException suppressed = null;
            try {
                clazz = parent.loadClass(className, false);
            } catch (ClassNotFoundException e) {
                suppressed = e;
            }

            if (clazz == null) {
                try {
                    clazz = findClass(className);
                } catch (ClassNotFoundException e) {
                    e.addSuppressed(suppressed);
                    throw e;
                }
            }
        }

        return clazz;
    }

直接調(diào)用findLoadedClass方法桶略,這個(gè)方法會(huì)去虛擬機(jī)中找當(dāng)前classloader是否已經(jīng)加載過(guò)該class了语淘,如果加載過(guò)了則直接返回這個(gè)class,否則返回null际歼,并去調(diào)用parent的loadClass方法惶翻,依次類(lèi)推,會(huì)一直找到根classloader的findLoadedClass方法吕粗,因?yàn)槲覀冞@里是第一次去加載sd卡上的自定義插件,所以肯定沒(méi)被加載過(guò),那么直到根classloader.findLoadedClass將一直返回null靶剑,然后會(huì)去調(diào) findClass(className)方法收夸,我們先看一下findLoadedClass方法吧:

 protected final Class<?> findLoadedClass(String className) {
        ClassLoader loader;
        if (this == BootClassLoader.getInstance())
            loader = null;
        else
            loader = this;
        return VMClassLoader.findLoadedClass(loader, className);
    }

BootClassLoader是ClassLoader的默認(rèn)parentloader,所以它是加載器鏈中的頂端设凹,也就是classloader的根節(jié)點(diǎn),因此它會(huì)最終代理一些方法到虛擬機(jī)去執(zhí)行,感興趣的同學(xué)可以去看下ClassLoader的源碼。這里說(shuō)一下,其實(shí)根節(jié)點(diǎn)我們剛才說(shuō)了是BootClassLoader,而且它重寫(xiě)了loadClass方法困鸥,因?yàn)樗鼪](méi)有parentloader鸟废,所以它沒(méi)有再去調(diào)用parent.loadClass(className, false),而是直接調(diào)用findClass方法,而findClass方法中也是直接返回Class.classForName(name, false, null)得到的值,顯然結(jié)果是null。

我們構(gòu)造DexClassLoader的時(shí)候給的parentloader參數(shù)是context.getClassLoader()该押,它其實(shí)就是加載我們應(yīng)用的類(lèi)加載器闻牡,也就是PathClassLoader翼馆,它的parentloader就是BootClassLoader猜极,這個(gè)具體代碼我沒(méi)去找,可以簡(jiǎn)單的打印下日志就能發(fā)現(xiàn),不過(guò)這個(gè)情況在instant run 的情況下會(huì)有所不同蕊蝗,大家可以去研究研究~有點(diǎn)扯遠(yuǎn)了,這里我想說(shuō)的是我們傳入的應(yīng)用類(lèi)加載器中通過(guò)findClass也是加載不到我們需要的類(lèi)的,所以我們還是得去看我們自定義的DexClassLoader中的findClass方法,DexClassLoader中還是沒(méi)有重寫(xiě)這個(gè)方法,那么還是去BaseDexClassLoader中看吧:

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        Class clazz = pathList.findClass(name);
        if (clazz == null) {
            throw new ClassNotFoundException(name);
        }
        return clazz;
    }

還記得我們的pathList嗎忽匈?不記得回頭看看上面的圖:

    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;
    }

Element是不是也很熟悉郭厌?就是那個(gè)簡(jiǎn)單的實(shí)體類(lèi),所以繼續(xù)到DexFile中去看dex.loadClassBinaryName方法:

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

最終使用了native方法defineClass去加載我們需要的類(lèi)巷懈,注意參數(shù)里有個(gè)mCookie欧引,它就是openDexFile方法返回的dex文件返回的int值因痛,這樣就將我們需要加載的類(lèi)和我們的dex文件對(duì)應(yīng)起來(lái)了,去從我們給定的dex文件中去加載我們需要的類(lèi)。

至此歹垫,我們的加載過(guò)程就很清楚了欲低。本文所提及的內(nèi)容并沒(méi)有什么技術(shù)含量,只是順著一條主線梳理了下整個(gè)過(guò)程财松,對(duì)于搞明白一些細(xì)節(jié)可能還是有點(diǎn)幫助喷户。順帶說(shuō)一下龟虎,其實(shí)熱修復(fù)也是用到了這個(gè)加載機(jī)制,動(dòng)態(tài)的替換掉有bug的類(lèi)衷模,因?yàn)橐呀?jīng)被加載過(guò)的類(lèi)不會(huì)再被加載镊叁,所以只要我們把修復(fù)bug后的類(lèi)放到有bug那個(gè)類(lèi)之前加載痰催,就能解決問(wèn)題了缝裁。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末加缘,一起剝皮案震驚了整個(gè)濱河市摆昧,隨后出現(xiàn)的幾起案子脚猾,更是在濱河造成了極大的恐慌提鸟,老刑警劉巖昆禽,帶你破解...
    沈念sama閱讀 206,968評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異偷俭,居然都是意外死亡拆祈,警方通過(guò)查閱死者的電腦和手機(jī)弄诲,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人伸刃,你說(shuō)我怎么就攤上這事景图。” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 153,220評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵湿硝,是天一觀的道長(zhǎng)示括。 經(jīng)常有香客問(wèn)我,道長(zhǎng)吼拥,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,416評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘测砂。我一直安慰自己加匈,他們只是感情好纵东,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,425評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布甜橱。 她就那樣靜靜地躺著子檀,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上习贫,一...
    開(kāi)封第一講書(shū)人閱讀 49,144評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼瞻凤。 笑死,一個(gè)胖子當(dāng)著我的面吹牛瞻坝,可吹牛的內(nèi)容都是我干的蛛壳。 我是一名探鬼主播,決...
    沈念sama閱讀 38,432評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼所刀,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼衙荐!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起浮创,我...
    開(kāi)封第一講書(shū)人閱讀 37,088評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤忧吟,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后斩披,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體溜族,經(jīng)...
    沈念sama閱讀 43,586評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,028評(píng)論 2 325
  • 正文 我和宋清朗相戀三年垦沉,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了煌抒。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,137評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡厕倍,死狀恐怖寡壮,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤况既,帶...
    沈念sama閱讀 33,783評(píng)論 4 324
  • 正文 年R本政府宣布屋群,位于F島的核電站,受9級(jí)特大地震影響坏挠,放射性物質(zhì)發(fā)生泄漏芍躏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,343評(píng)論 3 307
  • 文/蒙蒙 一降狠、第九天 我趴在偏房一處隱蔽的房頂上張望对竣。 院中可真熱鬧,春花似錦榜配、人聲如沸否纬。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,333評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)临燃。三九已至,卻和暖如春烙心,著一層夾襖步出監(jiān)牢的瞬間膜廊,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,559評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工淫茵, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留爪瓜,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,595評(píng)論 2 355
  • 正文 我出身青樓匙瘪,卻偏偏與公主長(zhǎng)得像铆铆,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子丹喻,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,901評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容