Android BaseDexClassLoader源碼閱讀

前言

Java的類加載使用父類加載機制,Android開發採用的同樣是Java語言,不過它並沒有採用JVM實現的ClassLoader類,Android內部使用的是BaseDexClassLoader、PathClassLoader、DexClassLoader三個類加載器實現從DEX文件中讀取類數據,其中PathClassLoader和DexClassLoader都是繼承自BaseDexClassLoader實現,這裏就分析一下它的實現代碼。

Android ClassLoader源碼

從前面的Java類委託機制直到ClassLoader的findClass方法就是在父類加載器無法加載的時候自己加載類的實現,在findClass方法裏會將加載類的工作委託給DexPathList類實現。

public class BaseDexClassLoader extends ClassLoader {
    // 需要加載的dex列表
    private final DexPathList pathList;
    // dexPath要加載的dex文件所在的路徑,optimizedDirectory是odex將dexPath
    // 處dex優化後輸出到的路徑,這個路徑必須是手機內部路勁,libraryPath是需要
    // 加載的C/C++庫路徑,parent是父類加載器對象
    public BaseDexClassLoader(String dexPath, File optimizedDirectory,
            String libraryPath, ClassLoader parent) {
        super(parent);
        this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory);
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        List<Throwable> suppressedExceptions = new ArrayList<Throwable>();
        // 使用pathList對象查找name類
        Class c = pathList.findClass(name, suppressedExceptions);
        return c;
    }
}

接着查看DexPathList的實現代碼,在內部會根據類加載器加載路徑查找dex文件,然後將它們解析成Element對象,Element對象代表的時dex文件或資源文件,它裏面保存了文件對象。

// Element類代表dex文件或資源文件的路徑元素
/*package*/ static class Element {
    private final File file;
    private final boolean isDirectory;
    private final File zip;
    private final DexFile dexFile;

    private ZipFile zipFile;
    private boolean initialized;

    // file文件,是否是目錄,zip文件通常都是apk或jar文件,dexFile就是.dex文件
    public Element(File file, boolean isDirectory, File zip, DexFile dexFile) {
        this.file = file;
        this.isDirectory = isDirectory;
        this.zip = zip;
        this.dexFile = dexFile;
    }
}

在DexPathList類構造的時候會首先將dexPath變量內容分隔成多個文件路徑,並且根據路徑查找Android中的dex和資源文件,將它們解析後存放到Element數組中。

/*package*/ final class DexPathList {
    private static final String DEX_SUFFIX = ".dex";
    private final ClassLoader definingContext;
    // 
    private final Element[] dexElements;
    // 本地庫目錄
    private final File[] nativeLibraryDirectories;

    public DexPathList(ClassLoader definingContext, String dexPath,
            String libraryPath, File optimizedDirectory) {
        // 當前類加載器的父類加載器
        this.definingContext = definingContext;
        ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>();
        // 根據輸入的dexPath創建dex元素對象
        this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,
                                           suppressedExceptions);
        if (suppressedExceptions.size() > 0) {
            this.dexElementsSuppressedExceptions =
                suppressedExceptions.toArray(new IOException[suppressedExceptions.size()]);
        } else {
            dexElementsSuppressedExceptions = null;
        }
        this.nativeLibraryDirectories = splitLibraryPath(libraryPath);
    }
}

makeDexElements就是把前面dexPath裏面解析到的路徑下的文件全部遍歷一遍,如果是dex文件或apk和jar文件就會查找它們內部的dex文件,將所有這些dex文件都加入到Element數組中,完成加載路徑下面的所有dex解析。

private static Element[] makeDexElements(ArrayList<File> files, File optimizedDirectory,
                                         ArrayList<IOException> suppressedExceptions) {
    ArrayList<Element> elements = new ArrayList<Element>();
    // 所有從dexPath找到的文件
    for (File file : files) {
        File zip = null;
        DexFile dex = null;
        String name = file.getName();
        // 如果是文件夾,就直接將路徑添加到Element中
        if (file.isDirectory()) {
            elements.add(new Element(file, true, null, null));
        } else if (file.isFile()){
            // 如果是文件且文件名以.dex結束
            if (name.endsWith(DEX_SUFFIX)) {
                try {
                    // 直接從.dex文件生成DexFile對象
                    dex = loadDexFile(file, optimizedDirectory);
                } catch (IOException ex) {
                    System.logE("Unable to load dex file: " + file, ex);
                }
            } else {
                zip = file;

                try {
                    // 從APK/JAR文件中讀取dex文件
                    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(file, false, zip, dex));
        }
    }

    return elements.toArray(new Element[elements.size()]);
}

在findClass方法裏查找名稱爲name的類時只需要遍歷Element數組找是dexFile就直接調用DexFile.loadClassBinaryName方法,這個方法能夠從dex文件數據中生成Class對象。

// 加載名字爲name的class對象
public Class findClass(String name, List<Throwable> suppressed) {
    // 遍歷從dexPath查詢到的dex和資源Element
    for (Element element : dexElements) {
        DexFile dex = element.dexFile;
        // 如果當前的Element是dex文件元素
        if (dex != null) {
            // 使用DexFile.loadClassBinaryName加載類
            Class clazz = dex.loadClassBinaryName(name, definingContext, suppressed);
            if (clazz != null) {
                return clazz;
            }
        }
    }
    if (dexElementsSuppressedExceptions != null) {
        suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
    }
    return null;
}

總結

BaseDexClassLoader在構造的時候會先將dexPath使用“:”分隔開,然後遍歷每個路徑下面的所有文件,查找到.dex文件.apk.jar類型的文件並將它們保存在Element數組中,當程序需要加載類的時候會直接遍歷所有的Element對象,查找到和dex文件相關的Element就直接加載數據生成Class對象。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章