ClassLoader詳解

一.Java中的ClassLoader。

80a46038ad7f148edef4bd2116d2ed21.png

1.Bootstrp loader
Bootstrp加載器是用C++語(yǔ)言寫的,它是在Java虛擬機(jī)啟動(dòng)后初始化的,它主要
負(fù)責(zé)加載%JAVA_HOME%/jre/lib,-Xbootclasspath參數(shù)指定的路徑以
及%JAVA_HOME%/jre/classes中的類。
2.ExtClassLoader
Bootstrp loader加載ExtClassLoader,并且將ExtClassLoader的父加載器設(shè)置為
Bootstrp loader.ExtClassLoader是用Java寫的,具體來(lái)說(shuō)就是
sun.misc.Launcher$ExtClassLoader,ExtClassLoader主要加
載%JAVA_HOME%/jre/lib/ext,此路徑下的所有classes目錄以及java.ext.dirs系統(tǒng)
變量指定的路徑中類庫(kù)。
3.AppClassLoader
Bootstrp loader加載完ExtClassLoader后,就會(huì)加載AppClassLoader,并且將
AppClassLoader的父加載器指定為 ExtClassLoader。AppClassLoader也是用
Java寫成的,它的實(shí)現(xiàn)類是 sun.misc.Launcher$AppClassLoader,另外我們知道
ClassLoader中有個(gè)getSystemClassLoader方法,此方法返回的正是
AppclassLoader.AppClassLoader主要負(fù)責(zé)加載classpath所指定的位置的類或者
是jar文檔,它也是Java程序默認(rèn)的類加載器。
4.CustomClassLoader
Java中提供的默認(rèn)ClassLoader,只加載指定目錄下的jar和class,如果我們想加
載其它位置的類或jar時(shí),比如:我要加載網(wǎng)絡(luò)上的一個(gè)class文件,通過(guò)動(dòng)態(tài)加載
到內(nèi)存之后,要調(diào)用這個(gè)類中的方法實(shí)現(xiàn)我的業(yè)務(wù)邏輯。在這樣的情況下,默認(rèn)
的ClassLoader就不能滿足我們的需求了,所以需要定義自己的ClassLoader。

二.Android中的ClassLoader詳解.

1.android中ClassLoader的種類。
a.BootClassLoader:主要用來(lái)加載android FrameWork層的一些class字節(jié)碼文件。
b.PathClassLoader:主要用來(lái)加載已經(jīng)安裝到系統(tǒng)中的APK 文件中的class(與java中的AppClassLoader類似)。
c.DexClassLoader:用來(lái)加載指定目錄下的class字節(jié)碼文件(與java中的自定義ClassLoader類似)。
d.BaseDexClassLoader:父類,PathClassLoader和DexClassLoader都是它的子類。
2.android中ClassLoader的特點(diǎn)和作用。
特點(diǎn):
雙親代理模式加載class字節(jié)碼。
雙親代理模式:ClassLoader在加載一個(gè)字節(jié)碼時(shí),首先會(huì)詢問(wèn) 當(dāng)前的
ClassLoader是否已經(jīng)加載過(guò)此類,如果已經(jīng)加載過(guò)就直接返回,不在重復(fù)的去
加載,如果沒(méi)有的話,會(huì)查詢它的parent是否已經(jīng)加載過(guò)此類,如果加載過(guò)那
么就直接返回parent加載過(guò)的字節(jié)碼文件,如果整個(gè)繼承線路上都沒(méi)有加載過(guò)
此類,最后由子ClassLoader執(zhí)行真正的加載。這樣做的好處:如果一個(gè)類被位
于樹中的任意ClassLoader節(jié)點(diǎn)加載過(guò),就會(huì)緩存在內(nèi)存里,那么在以后的整個(gè)
系統(tǒng)的生命周期中這個(gè)類都不會(huì)在被重新加載,大大提高了加載類的效率。
作用:
類加載共享功能
類加載的隔離功能:不同繼承線路上加載的類肯定不是同一個(gè)類,避免用戶自己
去寫一些代碼,冒充我們核心的類庫(kù)。
問(wèn)題:怎么樣的類才被認(rèn)為是同一個(gè)類?
包名和類名相同就可以嗎?不是,還要再加上是被同一個(gè)ClassLoader加載的。
三個(gè)條件都滿足,才能說(shuō)是同一個(gè)類。
3.ClassLoader源碼。
我們首先看一下ClassLoader中的最核心的方法loadClass。

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

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

1.首先調(diào)用findLoadedClass查看自身是否加載過(guò)該name的類文件。
2.如果沒(méi)有,調(diào)用父ClassLoader的loadClass看是否加載過(guò)類文件。
3.如果父classLoader也沒(méi)有加載過(guò),表明我們這個(gè)類從來(lái)沒(méi)有沒(méi)加載過(guò),則調(diào)用自身的findClass方法去dex文件中查找這個(gè)類。
4.以上就是雙親委托模式,首先看自己加載過(guò)沒(méi)有,如果沒(méi)有就看父classLoader加載過(guò)沒(méi)有。
接下來(lái)看一下findClass方法是如何實(shí)現(xiàn)的。

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

該方法是個(gè)空實(shí)現(xiàn),這樣做的意義是交給他的子類去實(shí)現(xiàn),這樣子類就可以定義不同的查找行為。
接下來(lái)看一下子類的實(shí)現(xiàn)。
DexClassLoader:

public DexClassLoader(String dexPath, String optimizedDirectory,
            String libraryPath, ClassLoader parent) {
        super(dexPath, new File(optimizedDirectory), libraryPath, parent);
}

DexClassLoader實(shí)現(xiàn)非常簡(jiǎn)單,只有一個(gè)構(gòu)造方法,該ClassLoader可以加載來(lái)自jar和apk中的dex文件中的類,因此DexClassLoader可以加載一些并沒(méi)有安裝到系統(tǒng)應(yīng)用中的一些類 所以說(shuō)DexClassLoader是我們動(dòng)態(tài)加載的核心。
dexPath:指定要加載的dex文件的路徑,包含多個(gè)路徑用File.pathSeparator間隔開,在Android上默認(rèn)是 ":" 。
optimizedDirectory:從apk中解析出dex文件存儲(chǔ)的路徑,一般是應(yīng)用程序內(nèi)部路徑 。
libraryPath:目標(biāo)類中使用的C/C++庫(kù)的列表,每個(gè)目錄用File.pathSeparator間隔開; 可以為 null。
parent:該類裝載器的父裝載器,一般用當(dāng)前執(zhí)行類的裝載器。
接下來(lái)看一下同樣繼承BaseDexClassLoader類的PathClassLoader。
PathClassLoader同樣很簡(jiǎn)單,有兩個(gè)構(gòu)造方法。

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

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

我們主要看一下第二個(gè)構(gòu)造方法。
與DexClassLoader構(gòu)造方法唯一的區(qū)別就是少了要拷貝的dex文件的路徑,因此PathClassLoader只能加載安裝到系統(tǒng)中的APK文件。
接下來(lái)看一下這兩個(gè)類的父類BaseDexClassLoader是如何完成類的查找的。

private final DexPathList pathList;

BaseDexClassLoader類里面定義了一個(gè)DexPathList,存儲(chǔ)的是dex的集合,因?yàn)閍pk是可以dex分包,它里面含有一個(gè)DexElement的集合,每一個(gè)Element就對(duì)應(yīng)一個(gè)dex文件,先看一下核心的方法findClass。

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

它是通過(guò)成員變量pathList的findClass方法 ,傳入我們要查找的類名,來(lái)尋找我們的class字節(jié)碼 ,所以說(shuō)BaseDexClassLoader也不是真正查找類的地方,接下來(lái)就到DexPathList中去看一下類是如何查找的。
我們首先看一下它的一些成員變量。

private static final String DEX_SUFFIX = ".dex";
private final ClassLoader definingContext;
private final Element[] dexElements;

DEX_SUFFIX:表示要加載的文件都是.dex后綴。
definingContext:在構(gòu)造方法中傳入的 。
dexElements:Element是DexPathList中的一個(gè)內(nèi)部類,里面最核心的就是DexFile。
接下來(lái)看一下構(gòu)造方法:

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;

        ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>();
        // save dexPath for BaseDexClassLoader
        this.dexElements = makePathElements(splitDexPath(dexPath), optimizedDirectory,suppressedExceptions);

        this.nativeLibraryDirectories = splitPaths(libraryPath, false);
        this.systemNativeLibraryDirectories =
                splitPaths(System.getProperty("java.library.path"), true);
        List<File> allNativeLibraryDirectories = new ArrayList<>(nativeLibraryDirectories);
        allNativeLibraryDirectories.addAll(systemNativeLibraryDirectories);

        this.nativeLibraryPathElements = makePathElements(allNativeLibraryDirectories, null,
                suppressedExceptions);

        if (suppressedExceptions.size() > 0) {
            this.dexElementsSuppressedExceptions =
                    suppressedExceptions.toArray(new IOException[suppressedExceptions.size()]);
        } else {
            dexElementsSuppressedExceptions = null;
        }
    }

初始化一些成員變量,this.definingContext賦值,最重要的就是this.dexElements = makePathElements(splitDexPath(dexPath), optimizedDirectory,suppressedExceptions);這行代碼,來(lái)初始化Element數(shù)組,下面看下該方法的實(shí)現(xiàn)。

private static Element[] makePathElements(List<File> files, File optimizedDirectory, List<IOException> suppressedExceptions) {
        List<Element> elements = new ArrayList<>();

        for (File file : files) {
            File zip = null;
            File dir = new File("");
            DexFile dex = null;
            String path = file.getPath();
            String name = file.getName();

            if (path.contains(zipSeparator)) {
                String split[] = path.split(zipSeparator, 2);
                zip = new File(split[0]);
                dir = new File(split[1]);
            } else if (file.isDirectory()) {
                ng up resources in directories is useful for running libcore tests.
                        elements.add(new Element(file, true, null, null));
            } else if (file.isFile()) {
                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 {
                    zip = file;
                    try {
                        dex = loadDexFile(file, optimizedDirectory);
                    } catch (IOException suppressed) {
                        suppressedExceptions.add(suppressed);
                    }
                }
            } else {
                System.logW("ClassLoader referenced unknown path: " + file);
            }
            if ((zip != null) || (dex != null)) {
                elements.add(new Element(dir, false, zip, dex));
            }
        }
        return elements.toArray(new Element[elements.size()]);
    }

該方法就是解析dex文件成對(duì)應(yīng)的DexElement
1.遍歷所有的dex文件。
2.將dex文件轉(zhuǎn)化成對(duì)應(yīng)的DexFile對(duì)象。
3.如果是.dex結(jié)尾(針對(duì)PathClassLoader處理),調(diào)用loadDexFile方法。
4.如果.dex .jar .apk 結(jié)尾 (針對(duì)DexClassLoader處理) ,調(diào)用loadDexFile方法。

三.Android中的動(dòng)態(tài)加載比一般Java程序復(fù)雜在哪里

1.有許多組件類需要注冊(cè)才能使用(Activity、Service需要在AndroidManifest中注
   冊(cè)后才能使用)。
2.資源的動(dòng)態(tài)加載比較復(fù)雜。(資源也需要注冊(cè))
總結(jié)來(lái)說(shuō)就是android的動(dòng)態(tài)加載需要一個(gè)上下文環(huán)境。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容