Android ClassLoader

类加载器

Posted by chchaooo on February 14, 2019

Android classloader

Android的ClassLoader和Java的ClassLoader有一些差异:

  • Java中的ClassLoader可以加载jar文件和Class文件(本质都是加载Class文件)
  • Android中ClassLoader加载的不再是Class文件,而是dex文件(DVM,ART都是如此)

同Java ClassLoader类似,也分为系统ClassLoader和自定义ClassLoader

系统ClassLoader包括

PathClassLoader

应用启动时创建,会加载data/app/目录下的dex文件。App安装到手机后,apk里面的class.dex中的class均是通过 PathClassLoader来加载的。该类继承自BaseDexClassLoader,其本身除了两个构造函数之外没有其他逻辑。

DexClassLoader

可以加载dex文件以及包含dex的apk文件或jar文件 也支持从SD卡进行加载,即可以在应用未安装的情况下加载dex,其中也只有一个构造函数

BaseDexClassLoader

public class BaseDexClassLoader extends ClassLoader {
    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;
    }
}

BaseClassLoader中最核心的的逻辑就是上面这些,可以看到findClass的逻辑是封装在DexPathList中的,BaseClassLoader中只是调用了下DexPathList的findClass方法。

DexPathList的功能是:

它的构造函数

/**
 * dexPath: /storage/emulated/0/testdex.jar                   
 * optimizedDirectory: /data/data/demo.ccccchen.com.dexdemo/app_temp
 * parent classloader: dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/demo.ccccchen.com.dexdemo-2/base.apk"],nativeLibraryDirectories=[/vendor/lib64, /system/lib64]]]
*/
public DexPathList(ClassLoader definingContext, String dexPath, String librarySearchPath, File optimizedDirectory){
    ...
}

BaseDexClassLoader在创建DexPathList时,传入的optimizedDirectory直接传了一个null进来,关于这个字段的作用后续再关注。

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

上面是DexPathList的构造函数,最重要的一句是makeDexElements(splitDexPath(dexPath)..),这里是将dexPath中的文件遍历一遍,如果是apk、dex或者jar文件,则将这些文件都加入到dexElements数组中。dexElements存储着所有的dex、apk、jar包文件

    /**
     * List of dex/resource (class path) elements.
     * Should be called pathElements, but the Facebook app uses reflection
     * to modify 'dexElements' (http://b/7726934).
     */
    private Element[] dexElements;
    
    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;
    }

而makeDexElements的源码是这样子的

private static Element[] makeDexElements(ArrayList<File> files, File optimizedDirectory,
                                             ArrayList<IOException> suppressedExceptions) {
        ArrayList<Element> elements = new ArrayList<Element>();
        /*
         * Open all files and load the (direct or contained) dex files
         * up front.
         */
        for (File file : files) {
            File 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)) {
                zip = file;

                try {
                    dex = loadDexFile(file, optimizedDirectory);
                } catch (IOException suppressed) {
                    /*
                     * 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).
                     * Let dex == null and hang on to the exception to add to the tea-leaves for
                     * when findClass returns null.
                     */
                    suppressedExceptions.add(suppressed);
                }
            } else if (file.isDirectory()) {
                // We support directories for looking up resources.
                // This is only useful for running libcore tests.
                elements.add(new Element(file, true, null, null));
            } else {
                System.logW("Unknown file type for: " + 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对象

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

DexFile中的loadClassBinaryName中最终解析出Class的代码是调用的native代码

    private static native Class defineClassNative(String name, ClassLoader loader, int cookie)
        throws ClassNotFoundException, NoClassDefFoundError;

总结

这些类中,最重要的一个元素就是这个dexElements,额外的dex、apk、jar包,只要以正确的方式被加入到这个数组中,那么,其中的类就都可以找到了。