ClassLoader 三 Android 类加载

来源:互联网 发布:杭州云计算产业园 编辑:程序博客网 时间:2024/06/08 17:31

一 前言

Android类加载同样遵循jvm虚拟机规范,不同点是class不是被类加载器直接装载,而是先被打包成dex文件,然后交由BaseDexClassLoader来完成类加载。

二 分类

BaseDexClassLoader 包含两个子类
1. DexClassLoader
2. PathClassLoader

下面分别看下对应的源码 :

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

从源码对比来看,二者只是在构造函数中传入的参数不同,最终都是通过父类BaseDexClassLoader构造方法完成初始化,代码如下:

public BaseDexClassLoader(String dexPath, File optimizedDirectory,            String libraryPath, ClassLoader parent) {        super(parent);        this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory);    }

BaseDexClassLoader构造方法有四个参数:
1. dexPath dex文件存放的路径
2. optimizedDirectory 优化后的dex存放路径
3. libraryPath 动态库的存放路径
4. parent 父亲类加载器

1 3 4都好理解,2相对特别一些,也是PathClassLoader想较DexClassLoader的不同的地方,PathClassLoader传入的optimizedDirectory是null。optimizedDirectory有什么作用呢?先继续跟踪代码看下去

BaseDexClassLoader构造方法里面只执行了一句

 this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory);

创建一个DexPathList对象,跟踪这个类

/** * A pair of lists of entries, associated with a {@code ClassLoader}. * One of the lists is a dex/resource path &mdash; typically referred * to as a "class path" &mdash; list, and the other names directories * containing native code libraries. Class path entries may be any of: * a {@code .jar} or {@code .zip} file containing an optional * top-level {@code classes.dex} file as well as arbitrary resources, * or a plain {@code .dex} file (with no possibility of associated * resources). * * <p>This class also contains methods to use these lists to look up * classes and resources.</p> *//*package*/ final class DexPathList {    /**     * List of dex/resource (class path) elements.     * Should be called pathElements, but the Facebook app uses reflection     * to modify 'dexElements' (http://b/7726934).     */    private final Element[] dexElements;    /** List of native library directories. */    private final File[] nativeLibraryDirectories;

DexPathList提供以下功能:
1. dex/资源元素列表
2. 动态库文件列表
3. 提供了查找类和资源的方法

2 3 相对容易理解,主要看1,对应代码如下:

 /**     * Makes an array of dex/resource path elements, one per element of     * the given array.     */    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()]);    }

该方法主要目标是创建一个dexfile,然后转换成element数组。下面来看dexfile的加载

 /**     * Constructs a {@code DexFile} instance, as appropriate depending     * on whether {@code optimizedDirectory} is {@code null}.     */    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);        }    }

根据optimizedDirectory是否为null,分别创建不同的DexFile对象。

现在回头来看optimizedDirectory的作用。一个apk文件在安卓设备上有两种存在状态,一种是已经安装了,一种是作为普通文件存放在sdcard上面。apk安装以后会在/data/目录生成一些文件,比如:

/data/app 存放了apk文件

/data/data 存放apk运行过程生成的一些缓存文件

/data/dalvik-cache 存放apk里面的dex文件

因此,假如apk已经安装了,那么jvm加载dex的时候无须指定optimizedDirectory,只需要到/data/dalvik-cache来查找就行了,因此这种情况可以使用PathClassLoader。反之,apk没有安装,那么在加载apk文件的时候需要将zip包里面的dex文件解压出来,存放到一个内部目录供后续jvm加载,这也就是optimizedDirectory的存在意义。

三 总结

至此,通过源码跟踪大致了解了android类加载机制的组成。主要是由BaseDexClassLoader来处理,根据apk是否安装到了设备上面又分为两种类加载器:DexClassLoader和PathClassLoader。

后面可以对DexClassLoader进一步学习,看看它是怎么如何在热修复上发挥重要作用的。

原创粉丝点击