###一. Android中ClassLoader的种类
Android的ClassLoader与Java的ClassLoader基本是一一对应的。如果对Java的ClassLoader不是很了解,可以参考《【Java 虚拟机】类加载器》。

  • BootClassLoader(Java的BootStrap ClassLoader)
    用于加载Android Framework层class文件。
  • PathClassLoader(Java的App ClassLoader)
    用于加载已经安装到系统中的apk中的class文件。
  • DexClassLoader(Java的Custom ClassLoader)
    用于加载指定目录中的class文件。
  • BaseDexClassLoader
    是PathClassLoader和DexClassLoader的父类。
    ###二. Android中ClassLoader的特点
    遵循双亲委派模型

ClassLoader在加载一个class文件时:会询问当前ClassLoader是否已经加载过子类,如果已经加载过则直接返回,不再重复加载。如果没有加载过,会去查询当前ClassLoader的parent是否已经加载过。

因为遵循双亲委派模型,Android中的classLoader具有两个特点:

  • 类加载共享
    当一个class文件被任何一个ClassLoader加载过,就不会再被其他ClassLoader加载。
  • 类加载隔离
    不同ClassLoader加载的class文件肯定不是一个。举个栗子,一些系统层级的class文件在系统初始化的时候被加载,比如java.net.String,这个是在应用启动前就被系统加载好的。如果在一个应用里能简单地用一个自定义的String类把这个String类替换掉的话,将有严重的安全问题。
    ###三. ClassLoader源码详解
    我们从ClassLoader.java的loadClass()方法看起。我们知道Android的ClassLoader是实现了双亲委派模型的,我们来从源码角度来看下是如何实现的。
protected Class<?> loadClass(String name, boolean resolve)        throws ClassNotFoundException    {            // First, check if the class has already been loaded            Class c = findLoadedClass(name);            if (c == null) {                long t0 = System.nanoTime();                try {                    if (parent != null) {                        c = parent.loadClass(name, false);                    } else {                        c = findBootstrapClassOrNull(name);                    }                } catch (ClassNotFoundException e) {                    // ClassNotFoundException thrown if class not found                    // from the non-null parent class loader                }                if (c == null) {                    // If still not found, then invoke findClass in order                    // to find the class.                    long t1 = System.nanoTime();                    c = findClass(name);                    // this is the defining class loader; record the stats                }            }            return c;    }

可以看到,先是判断ClassLoader自身是否加载过该class文件,如果没有再判断父ClassLoader是否加载过,如果都没有加载过再自己去加载。这和我们上述的双亲委派模型思想完全一致。
好了,我们来看下ClassLoader是如何去加载class文件的呢?也就是去看下findClass()方法的具体实现。

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

很遗憾,findClass()方法是一个空实现,也就是说它的具体实现是交给子类的。
【Android热修复与插件化 三】ClassLoader详解_第1张图片
如图,可以看到DexClassLoader和PathClassLoader是ClassLoader的间接实现类。
所以,下面我们来着重讲解一下DexClassLoader和PathClassLoader的源码。
###四. DexClassLoader、PathClassLoader、BaseDexClassLoader源码详解

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

可以看到DexClassLoader的源码非常简单,只有一个构造方法。我们来看下其四个参数都是什么含义。

  • dexPath。要加载的dex文件路径。
  • optimizedDirectory。dex文件要被copy到的目录路径。
  • libraryPath。apk文件中类要使用的c/c++代码。
  • parent。父装载器,也就是真正loadclass的装载器。

我们接下来看PathClassLoader的源码。

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

它的源码也只是有两个构造方法,我们来看第二个构造方法,可以看出,它与DexClassLoader的构造方法的区别就是少了一个要把dex文件copy到的目录路径。正是因为缺少这个路径,我们的PathClassLoader只能用来加载安装过的apk中的dex文件。
这两个ClassLoader的真正核心方法都在BaseDexClassLoader中,我们现在来看下源码。

public class BaseDexClassLoader extends ClassLoader {    private final DexPathList pathList;    public BaseDexClassLoader(String dexPath, File optimizedDirectory,            String librarySearchPath, ClassLoader parent) {        super(parent);        this.pathList = new DexPathList(this, dexPath, librarySearchPath, optimizedDirectory);    }    @Override    protected Class<?> findClass(String name) throws ClassNotFoundException {        List suppressedExceptions = new ArrayList();        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;    }    /**     * @hide     */    public void addDexPath(String dexPath) {        pathList.addDexPath(dexPath, null /*optimizedDirectory*/);    }    @Override    protected URL findResource(String name) {        return pathList.findResource(name);    }    @Override    protected Enumeration findResources(String name) {        return pathList.findResources(name);    }    @Override    public String findLibrary(String name) {        return pathList.findLibrary(name);    }    protected synchronized Package getPackage(String name) {        if (name != null && !name.isEmpty()) {            Package pack = super.getPackage(name);            if (pack == null) {                pack = definePackage(name, "Unknown", "0.0", "Unknown",                        "Unknown", "0.0", "Unknown", null);            }            return pack;        }        return null;    }    /**     * @hide     */    public String getLdLibraryPath() {        StringBuilder result = new StringBuilder();        for (File directory : pathList.getNativeLibraryDirectories()) {            if (result.length() > 0) {                result.append(':');            }            result.append(directory);        }        return result.toString();    }    @Override public String toString() {        return getClass().getName() + "[" + pathList + "]";    }}

可以看到,这个类的实现也是比较简单。我们来分析一下。
我们看到BaseDexClassLoader有一个成员变量DexPathList,其次它的核心方法是findClass()。我们来看下具体实现。

@Override    protected Class<?> findClass(String name) throws ClassNotFoundException {        List suppressedExceptions = new ArrayList();        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;    }

我们看到,findClass其实是通过成员变量pathList的findClass()方法来查找的。
所以,我们接下来还需要去看DexPathList的源码。
###五. DexPathList源码详解(重点)
由于DexPathList源码较长,我们这里分段讲解。
####1. 构造方法

    public DexPathList(ClassLoader definingContext, String dexPath,            String librarySearchPath, 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 suppressedExceptions = new ArrayList();        // save dexPath for BaseDexClassLoader        this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,                                           suppressedExceptions, definingContext);        this.nativeLibraryDirectories = splitPaths(librarySearchPath, false);        this.systemNativeLibraryDirectories =                splitPaths(System.getProperty("java.library.path"), true);        List allNativeLibraryDirectories = new ArrayList<>(nativeLibraryDirectories);        allNativeLibraryDirectories.addAll(systemNativeLibraryDirectories);        this.nativeLibraryPathElements = makePathElements(allNativeLibraryDirectories,                                                          suppressedExceptions,                                                          definingContext);        if (suppressedExceptions.size() > 0) {            this.dexElementsSuppressedExceptions =                suppressedExceptions.toArray(new IOException[suppressedExceptions.size()]);        } else {            dexElementsSuppressedExceptions = null;        }    }

构造方法里最重要的一行代码是

this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,                                           suppressedExceptions, definingContext);

这行代码的意思是构造一个Element数组。那么Element是什么呢?它是DexPathList的一个内部类。

static class Element {        private final File dir;        private final boolean isDirectory;        private final File zip;        private final DexFile dexFile;        private ClassPathURLStreamHandler urlHandler;        private boolean initialized;        public Element(File dir, boolean isDirectory, File zip, DexFile dexFile) {            this.dir = dir;            this.isDirectory = isDirectory;            this.zip = zip;            this.dexFile = dexFile;        }        @Override public String toString() {            if (isDirectory) {                return "directory \"" + dir + "\"";            } else if (zip != null) {                return "zip file \"" + zip + "\"" +                       (dir != null && !dir.getPath().isEmpty() ? ", dir \"" + dir + "\"" : "");            } else {                return "dex file \"" + dexFile + "\"";            }        }        public synchronized void maybeInit() {            if (initialized) {                return;            }            initialized = true;            if (isDirectory || zip == null) {                return;            }            try {                urlHandler = new ClassPathURLStreamHandler(zip.getPath());            } catch (IOException ioe) {                /*                 * 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: " + zip, ioe);                urlHandler = null;            }        }        public String findNativeLibrary(String name) {            maybeInit();            if (isDirectory) {                String path = new File(dir, name).getPath();                if (IoUtils.canOpenReadOnly(path)) {                    return path;                }            } else if (urlHandler != null) {                // Having a urlHandler means the element has a zip file.                // In this case Android supports loading the library iff                // it is stored in the zip uncompressed.                String entryName = new File(dir, name).getPath();                if (urlHandler.isEntryStored(entryName)) {                  return zip.getPath() + zipSeparator + entryName;                }            }            return null;        }        public URL findResource(String name) {            maybeInit();            // We support directories so we can run tests and/or legacy code            // that uses Class.getResource.            if (isDirectory) {                File resourceFile = new File(dir, name);                if (resourceFile.exists()) {                    try {                        return resourceFile.toURI().toURL();                    } catch (MalformedURLException ex) {                        throw new RuntimeException(ex);                    }                }            }            if (urlHandler == null) {                /* This element has no zip/jar file.                 */                return null;            }            return urlHandler.getEntryUrlOrNull(name);        }    }

其中,它有一个非常重要的成员变量DexFile。
接着回到主流程,我们通过makeDexElements()方法得到了一个elements数组。那么,makeDexElements()方法具体干了什么呢?

private static Element[] makeElements(List files, File optimizedDirectory,                                          List suppressedExceptions,                                          boolean ignoreDexFiles,                                          ClassLoader loader) {        Element[] elements = new Element[files.size()];        int elementsPos = 0;        /*         * Open all files and load the (direct or contained) dex files         * up front.         */        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()) {                // We support directories for looking up resources and native libraries.                // Looking up resources in directories is useful for running libcore tests.                elements[elementsPos++] = new Element(file, true, null, null);            } else if (file.isFile()) {                if (!ignoreDexFiles && name.endsWith(DEX_SUFFIX)) {                    // Raw dex file (not inside a zip/jar).                    try {                        dex = loadDexFile(file, optimizedDirectory, loader, elements);                    } catch (IOException suppressed) {                        System.logE("Unable to load dex file: " + file, suppressed);                        suppressedExceptions.add(suppressed);                    }                } else {                    zip = file;                    if (!ignoreDexFiles) {                        try {                            dex = loadDexFile(file, optimizedDirectory, loader, elements);                        } catch (IOException suppressed) {                            suppressedExceptions.add(suppressed);                        }                    }                }            } else {                System.logW("ClassLoader referenced unknown path: " + file);            }            if ((zip != null) || (dex != null)) {                elements[elementsPos++] = new Element(dir, false, zip, dex);            }        }        if (elementsPos != elements.length) {            elements = Arrays.copyOf(elements, elementsPos);        }        return elements;    }

它的作用是通过loadDexFile()方法把dex文件都加载出来,然后返回一个elements数组。
接着,我们来看DexPathList中最重要的方法findClass()。

public Class findClass(String name, List 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;    }

它就是通过遍历elements数组,拿到里面的每一个dex文件,通过DexFile的loadClassBinaryName()方法找到class字节码。通过查看DexFile源码,可以得知loadClassBinaryName()方法最终是调用的底层C++方法来load class。
至此,我们就完整地了解了ClassLoader加载class的具体实现。
###六. 总结
本文讲解了Android中几种ClassLoader的作用,并且从源码角度讲解了class的加载过程。在Android中加载class,其实最终是通过DexPathList的findClass来加载的。
另外,这里贴一下几个文件源码的查看地址。
http://androidxref.com/7.1.2_r36/xref/libcore/dalvik/src/main/java/dalvik/system/DexPathList.java
http://androidxref.com/7.1.2_r36/xref/libcore/dalvik/src/main/java/dalvik/system/DexClassLoader.java
http://androidxref.com/7.1.2_r36/xref/libcore/dalvik/src/main/java/dalvik/system/PathClassLoader.java
以上就是本文的全部内容,如有疑问,欢迎留言提问。

更多相关文章

  1. Android中TextView中内容不换行的解决方法
  2. Android模拟、实现、触发系统按键事件的方法
  3. android 2.2+ 完全退出程序的方法
  4. android读写文件
  5. android MediaPlayer出现RuntimeException: failure code: -38崩
  6. Android内核的根文件系统

随机推荐

  1. 代码设置android:icon,android:label
  2. layout_marginBottom的使用
  3. android sdk命令行操作笔记
  4. imagebutton 带文字
  5. android中ListView滑动变黑的情况
  6. Excessive JNI global references - Andr
  7. Android IPC 机制【1】--简介
  8. Android Studio第三十九期 - popWindows
  9. 【自定义控件】 ------- GridView
  10. android 修改主题背景色