Android ClassLoader 攻略

161 阅读10分钟

攻略大全

1. 粘贴攻略

2. 造火箭攻略

3. 拧螺丝攻略

3.1 Java中的ClassLoader

3.1.1 ClassLoader的类型

Java中的类加载器主要有两种类型,即系统类加载器和自定义类加载器。其中系统类加载器包括3种,分别是Bootstrap ClassLoader、Extensions ClassLoader和Application ClassLoader。

1.Bootstrap ClassLoader(引导类加载器)

C/C++代码实现的加载器,用于加载指定的JDK的核心类库,比如java.lang.、java.uti.等这些系统类。它用来加载以下目录中的类库:

  • $JAVA_HOME/jre/lib目录。
  • -Xbootclasspath参数指定的目录。

Java 虚拟机的启动就是通过Bootstrap ClassLoader 创建一个初始类来完成的。由于Bootstrap ClassLoader是使用C/C++语言实现的,所以该加载器不能被Java代码访问到。需要注意的是,Bootstrap ClassLoader并不继承java.lang.ClassLoader。

我们可以通过如下代码来得出Bootstrap ClassLoader所加载的目录:

image.png

打印结果为:

image.png

可以发现几乎都是$JAVA_HOME/jre/lib 目录中的jar 包,包括rt.jar、resources.jar 和charsets.jar等。

2.Extensions ClassLoader(拓展类加载器)

Java中的实现类为ExtClassLoader,因此可以简称为ExtClassLoader,它用于加载Java的拓展类,提供除了系统类之外的额外功能。ExtClassLoader用来加载以下目录中的类库:

  • 加载$JAVA_HOME/jre/lib/ext目录。
  • 系统属性java.ext.dir所指定的目录。

通过以下代码可以得到Extensions ClassLoader加载目录:

image.png

打印结果为:

image.png

3.Application ClassLoader(应用程序类加载器)

Java中的实现类为AppClassLoader,因此可以简称为AppClassLoader,同时它又可以称作System ClassLoader(系统类加载器),这是因为AppClassLoader可以通过ClassLoader的getSystemClassLoader方法获取到。它用来加载以下目录中的类库:

  • 当前程序的Classpath目录。
  • 系统属性java.class.path指定的目录。

4.Custom ClassLoader(自定义类加载器)

除了系统提供的类加载器,还可以自定义类加载器,自定义类加载器通过继承java.lang.ClassLoader 类的方式来实现自己的类加载器,Extensions ClassLoader 和App ClassLoader也继承了java.lang.ClassLoader类。

3.1.2 ClassLoader的继承关系

image.png

  • ClassLoader是一个抽象类,其中定义了ClassLoader的主要功能。

  • SecureClassLoader 继承了抽象类ClassLoader,但SecureClassLoader 并不是ClassLoader 的实现类,而是拓展了ClassLoader 类加入了权限方面的功能,加强了ClassLoader的安全性。

  • URLClassLoader继承自SecureClassLoader,可以通过URL路径从jar文件和文件夹中加载类和资源。

  • ExtClassLoader和AppClassLoader都继承自URLClassLoader,它们都是Launcher 的内部类,Launcher是Java虚拟机的入口应用,ExtClassLoader 和AppClassLoader都是在Launcher中进行初始化的。

3.1.3 双亲委托模式

类加载器查找Class 所采用的是双亲委托模式,所谓双亲委托模式就是首先判断该Class是否已经加载,如果没有则先委托给父加载器进行查找,这样依次进行递归,直到委托到最顶层的Bootstrap ClassLoader,如果BootstrapClassLoader找到了该Class,就会直接返回,如果没找到,则继续依次向下查找,如果还没找到则最后会交由自身去查找。

image.png

我们知道类加载子系统用来查找和加载Class文件到Java虚拟机中,假设我们要加载一个位于D盘的Class文件,系统所提供的类加载器不能满足条件,这时就需要我们自定义类加载器继承自java.lang.ClassLoader,并复写它的findClass方法。

加载D盘的Class文件步骤如下:

(1)自定义类加载器首先从缓存中查找Class 文件是否已经加载,如果已经加载就返回该Class,如果没加载则委托给父加载器也就是AppClassLoader。

(2)按照上图中虚线的方向递归步骤1。

(3)一直委托到BootstrapClassLoader,如果BootstrapClassLoader查找缓存也没有加载Class文件,则在$JAVA_HOME/jre/lib目录中或者--Xbootclasspath参数指定的目录中进行查找,如果找到就加载并返回该Class,如果没有找到则交给子加载器ExtClassLoader。

(4)ExtClassLoader在$JAVA_HOME/jre/lib/ext目录中或者系统属性java.ext.dir所指定的目录中进行查找,如果找到就加载并返回,找不到则交给AppClassLoader。

(5)AppClassLoade在Classpath目录中或者系统属性java.class.path指定的目录中进行查找,如果找到就加载并返回,找不到交给我们自定义的类加载器,如果还找不到则抛出异常。

总的来说就是Class文件加载到类加载子系统后,先沿着上图中虚线的方向自下而上进行委托,再沿着实线的方向自上而下进行查找和加载,整个过程就是先上后下。结合ClassLoader 的继承关系,可以得出ClassLoader 的父子关系并不是使用继承来实现的,而是使用组合来实现代码复用的。

类加载的步骤在JDK8的源码中也得到了体现,下面来查看抽象类ClassLoader的loadClass方法:

image.png

在注释1处用来检查传入的类是否已经加载,如果已经加载则后面的代码不会执行,最后会返回该加载类。

没有加载会接着向下执行,在注释2处,如果父类加载器不为null,就调用父类加载器的loadClass方法。

如果父类加载器为null就调用注释3处的findBootstrapClassOrNull方法,这个方法内部调用了Native方法findBootstrapClass,findBootstrapClass方法中最终会用BootstrapClassloader来检查该类是否已经加载。

如果没有加载就说明向上委托流程中没有加载该类,则调用注释4处的findClass方法继续向下进行查找流程。

采取双亲委托模式主要有如下两点好处:

  • 避免重复加载。
    • 如果已经加载过一次Class,就不需要再次加载,而是直接读取已经加载的Class。
  • 更加安全。
    • 如果不使用双亲委托模式,就可以自定义一个String 类来替代系统的String类,这显然会造成安全隐患,采用双亲委托模式会使得系统的String类在Java虚拟机启动时就被加载,也就无法自定义String类来替代系统的String类,除非我们修改类加载器搜索类的默认算法。还有一点,只有两个类名一致并且被同一个类加载器加载的类,Java虚拟机才会认为它们是同一个类,想要骗过Java虚拟机显然不会那么容易。

3.1.4 自定义ClassLoader

系统提供的类加载器只能够加载指定目录下的jar包和Class文件,如果想要加载网络上的或者磁盘某一文件中的jar包和Class文件则需要自定义ClassLoader。

实现自定义ClassLoader需要如下两个步骤:

(1)定义一个自定义ClassLoade并继承抽象类ClassLoader。

(2)复写findClass方法,并在findClass方法中调用defineClass方法。

3.2 Android中的ClassLoader

3.2.1 ClassLoader的类型

Android中的ClassLoader类型和Java中的ClassLoader类型类似,也分为两种类型,分别是系统类加载器和自定义加载器。其中系统类加载器主要包括3种,分别是BootClassLoader、PathClassLoader和DexClassLoader。

1.BootClassLoader

Android系统启动时会使用BootClassLoader来预加载常用类,与SDK中的BootstrapClassLoader 不同,它并不是由C/C++代码实现的,而是由Java 实现的,BootClassLoader的代码如下所示:

image.png

BootClassLoader是ClassLoader的内部类,并继承自ClassLoader。BootClassLoader是一个单例类,需要注意的是BootClassLoader的访问修饰符是默认的,只有在同一个包中才可以访问,因此我们在应用程序中是无法直接调用的。

2.DexClassLoader

DexClassLoader可以加载dex文件以及包含dex的压缩文件(apk和jar文件),不管加载哪种文件,最终都要加载dex文件,查看DexClassLoader的代码,如下所示:

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 this parameter is deprecated and has no effect since API level 26.
     * @param librarySearchPath 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 librarySearchPath, ClassLoader parent) {
        super(dexPath, null, librarySearchPath, parent);
    }
}

DexClassLoader的构造方法有如下4个参数:

  • dexPath:dex相关文件路径集合,多个路径用文件分隔符分隔,默认文件分隔符为“:”。

  • optimizedDirectory:解压的dex文件存储路径,这个路径必须是一个内部存储路径,在一般情况下,使用当前应用程序的私有路径:/data/data/<Package Name>/...。

  • librarySearchPath:包含C/C++库的路径集合,多个路径用文件分隔符分隔,可以为null。

  • parent:父加载器。

DexClassLoader 继承自BaseDexClassLoader,方法都在BaseDexClassLoader中实现。

3.PathClassLoader

Android系统使用PathClassLoader来加载系统类和应用程序的类,下面来查看它的代码:

image.png

PathClassLoader继承自BaseDexClassLoader,也都在BaseDexClassLoader中实现。

在PathClassLoader的构造方法中没有参数optimizedDirectory,这是因为PathClassLoader已经默认了参数optimizedDirectory的值为/data/dalvik-cache,很显然PathClassLoader无法定义解压的dex文件存储路径,因此PathClassLoader通常用来加载已经安装的apk的dex文件(安装的apk的dex文件会存储在/data/dalvik-cache中)。

3.2.2 ClassLoader的继承关系

image.png

  • ClassLoader 是一个抽象类,其中定义了ClassLoader 的主要功能。BootClassLoader是它的内部类。

  • SecureClassLoader类和JDK 8中的SecureClassLoader类的代码是一样的,它继承了抽象类ClassLoader。SecureClassLoader 并不是ClassLoader 的实现类,而是拓展了ClassLoader类加入了权限方面的功能,加强了ClassLoader的安全性。

  • URLClassLoader类和JDK 8中的URLClassLoader类的代码是一样的,它继承自SecureClassLoader,用来通过URL路径从jar文件和文件夹中加载类和资源。

  • InMemoryDexClassLoader是Android 8.0新增的类加载器,继承自BaseDexClassLoader,用于加载内存中的dex文件。

  • BaseDexClassLoader 继承自ClassLoader,是抽象类ClassLoader 的具体实现类,PathClassLoader、DexClassLoader和InMemoryDexClassLoader都继承自它。

3.3.3 ClassLoader的加载过程

Android的ClassLoader同样遵循了双亲委托模式,ClassLoader的加载方法为loadClass方法,这个方法被定义在抽象类ClassLoader中,如下所示:

image.png

在注释1处用来检查传入的类是否已经加载,如果已经加载就返回该类,

如果没有加载就在注释2处判断父加载器是否存在,存在就调用父加载器的loadClass方法,

如果不存在就调用注释3处的findBootstrapClassOrNull方法,这个方法会直接返回null。

如果注释4处的代码成立,说明向上委托流程没有检查出类已经被加载,就会执行注释5处的findClass方法来进行查找流程,

findClass方法如下所示:

image.png

在findClass 方法中直接抛出了异常,这说明findClass 方法需要子类来实现,BaseDexClassLoader的代码如下所示:

/**
 * @hide
 */
public BaseDexClassLoader(String dexPath, File optimizedDirectory,
                          String librarySearchPath, ClassLoader parent, boolean isTrusted) {
    super(parent);
    // 构造方法中创建了DexPathList
    this.pathList = new DexPathList(this, dexPath, librarySearchPath, null, isTrusted);

    if (reporter != null) {
        reportClassLoaderChain();
    }
}


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

查看DexPathList的findClass:

/**
 * 在此实例指向的 dex 文件之一中查找命名类。这将在最早列出的路径元素中找到一个。
 * 如果类已找到但尚未定义,则此方法将在构造此实例的定义上下文中定义它。
 *
 * @param name of class to find
 * @param suppressed exceptions encountered whilst finding the class
 * @return the named class or {@code null} if the class is not
 * found in any of the dex files
 */
public Class<?> findClass(String name, List<Throwable> suppressed) {
    // 遍历数组
    for (Element element : dexElements) {
        // 调用Element的findClass()
        Class<?> clazz = element.findClass(name, definingContext, suppressed);
        if (clazz != null) {
            return clazz;
        }
    }

    if (dexElementsSuppressedExceptions != null) {
        suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
    }
    return null;
}

Element是DexPathList的静态内部类:

image.png 从Element的构造方法可以看出,其内部封装了DexFile,它用于加载dex。在注释1处如果DexFile不为null就调用DexFile的loadClassBinaryName方法:

image.png 在loadClassBinaryName方法中调用了defineClass方法:

image.png

image.png

在注释1处调用了defineClassNative方法来加载dex相关文件,这个方法是Native方法。

ClassLoader的加载过程就是遵循着双亲委托模式,如果委托流程没有检查到此前加载过传入的类,就调用ClassLoader的findClass方法,Java层最终会调用DexFile的defineClassNative方法来执行查找流程,如图所示。

image.png

3.2.4 BootClassLoader的创建

image.png

image.png

main方法是ZygoteInit的入口方法,其中调用了ZygoteInit的preload方法,在preload方法中又调用了ZygoteInit的preloadClasses方法,如下所示:

image.png

image.png

preloadClasses方法用于Zygote进程初始化时预加载常用类。

在注释1处PRELOADED_CLASSES的值为将/system/etc/preloaded-classes文件封装成FileInputStream。

preloaded-classes 文件中存有预加载类的目录,这个文件在系统源码中的路径为frameworks/base/preloaded-classes,这里列举一些preloaded-classes文件中的预加载类名称,如下所示:

image.png

可以看到preloaded-classes文件中的预加载类的名称有很多都是我们非常熟知的。预加载属于拿空间换时间的策略,Zygote环境配置得越健全越通用,应用程序进程需要单独做的事情也就越少,预加载除了预加载类,还有预加载资源和预加载共享库。

回到preloadClasses方法的注释2处,将FileInputStream封装为BufferedReader,

并在注释3处遍历BufferedReader,读出所有预加载类的名称,每读出一个预加载类的名称就调用注释4处的代码加载该类,Class的forName方法如下所示:

image.png image.png

在注释1处创建了BootClassLoader,并将BootClassLoader实例传入到了注释2处的classForName方法中,

classForName方法是Native方法,它的实现由C/C++代码来完成,如下所示:

image.png

3.2.5 PathClassLoader的创建

PathClassLoader 的创建也得从Zygote 进程开始说起,Zygote 进程启动SystemServer进程时会调用ZygoteInit的startSystemServer方法,如下所示:

image.png image.png

在注释1处,Zygote进程通过forkSystemServer方法fork自身创建子进程(SystemServer进程)。

在注释2处如果forkSystemServer方法返回的pid等于0,说明当前代码是在新创建的SystemServer进程中执行的,

接着就会执行注释3处的handleSystemServerProcess方法:

image.png

在注释1处调用了createPathClassLoader方法,如下所示:

image.png

在createPathClassLoader 方法中又调用了PathClassLoaderFactory的createClassLoader方法,看来PathClassLoader是用工厂来进行创建的:

image.png

在PathClassLoaderFactory的createClassLoader方法中会创建PathClassLoader。讲到这里可以得出结论,PathClassLoader是在SystemServer进程中采用工厂模式创建的。

4. 复制攻略

4.1 《Android进阶解密》