Android进阶知识:类加载相关

5,273 阅读18分钟

1. 前言

类加载原理作为程序运行的基础,一直在程序的背后默默的付出。如今Android中的插件化、热修复等动态加载技术的实现也都涉及到了类加载的原理。关于类加载的相关知识我以前也是遇到一点看一点,没有完整的详细的了解过,最近有时间专门对这块知识进行了学习,于是这里做一个总结。

2. 类加载过程

一个类从.class文件被加载到内存,到在内存中使用,最后从内存中卸载,这是一个完整的生命周期过程。不过在获得.class文件之前,我们编码时的文件格式还是.java文件格式,还记得刚学Java时学到过在完成编码之后要先执行javac命令进行编译,编译生成对应的.class文件,之后再通过java命令执行Java程序。不过当时只知道是先编译再运行,并不知道到底是怎么运行的谁去运行的。

其实一个类从.class文件被加载到内存到从内存中卸载,整个生命周期一共经过以下几个阶段:

  1. 加载
  2. 连接(包含验证、准备、解析三个阶段)
  3. 初始化
  4. 使用
  5. 卸载

类加载生命周期

2.1 加载阶段

在加载阶段虚拟机主要完成以下三件事情:

  1. 通过一个类的全限定名来获取定义此类的二进制字节流。
  2. 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
  3. 在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。

这个简单的来说加载阶段主要就是将类的.class文件作为二进制字节流读入内存,并且在内存中实例化一个java.lang.Class对象以便后续访问。在这个阶段中.class文件二进制字节流的读取来源没有太多限制,可以非常灵活。比如可以从本地系统中读取、可以从jar包中读取、可以从网络下载等等。

2.2 连接—验证阶段

验证是连接阶段中的第一步,主要的作用是保证Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机的自身安全。在验证阶段大致会完成以下四个检验操作:

  1. 文件格式验证:验证字节流是否符合Class文件格式的规范,并且能被当前版本的虚拟机处理。
  2. 元数据验证:对字节码描述的信息进行语义分析,保证其描述符合Java语言规范的要求。
  3. 字节码验证:通过数据流和控制流分析,确定程序逻辑是合法的、符合罗急的。
  4. 符号引用验证:对类自身以外的信息进行匹配性校验。

从以上几个操作可以看出,这个阶段主要就是将二进制字节流进行一个合法验证,包括文件格式、语义、数据流控制流和符号引用等。保证不会出现类似文件格式错误、继承了被final修饰的类、指令跳转错误、类型转换错误、修饰符访问性等等错误情况。

2.3 连接—准备阶段

准备阶段中主要是为类中静态变量在方法区里分配内存并且设置类变量的初始值。这里的初始值即零值。具体如下:

数据类型 零值
int 0
long 0L
short (short)0
char '\u0000'
byte (byte)0
boolean false
float 0.0f
double 0.0d
reference null

2.4 连接—解析阶段

解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程。解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调试限定符7类符号引用进行。

2.5 初始化阶段

初始化阶段中真正开始执行类中定义的Java程序代码,为所有类变量进行赋值,执行静态代码块。

3. Java中类加载器

从上面的类加载过程可以看出在初始化阶段以前除了加载阶段,其余阶段都是由虚拟机主导控制。而在加载阶段可以通过自定义类加载器进行参与。类加载器顾名思义是用来加载类的,负责将类的.class文件转换成内存中类的二进制字节流。Java中的类加载器按类型分有两种:系统类加载器和自定义类加载器。其中系统类加载器有主要有三种,分别是:引导类加载器(Bootstrap ClassLoader)、拓展类加载器(Extensions ClassLoader)和应用程序类加载器(Application ClassLoader)。

3.1 系统类加载器

3.1.1 引导类加载器(Bootstrap ClassLoader

这个类加载器是用C/C++语言实现的,用来加载JDK中的核心类,主要加载$JAVA_HOME/jre/lib目录下的类,例如rt.jarresources.jar等包中的类。

3.1.2 拓展类加载器(Extensions ClassLoader

这个类加载器是用Java语言实现的,实现类为ExtClassLoader,用来加载Java的拓展类,主要加载$JAVA_HOME/jre/lib/ext目录和系统属性java.ext.dir所指定的目录。

3.1.3 应用程序类加载器(Application ClassLoader

这个类加载器是用Java语言实现的,实现类为AppClassLoader,可以通过ClassLoader.getSystemClassLoader方法获取到,主要加载Classpath目录和系统属性java.class.path指定的目录下的类。

3.1.4 ClassLoader的继承关系
public class ClassLoaderDemo {
    public static void main(String[] args) {
        ClassLoader classLoader = ClassLoaderDemo.class.getClassLoader();
        while (classLoader != null) {
            System.out.println("ClassLoader:" + classLoader);
            classLoader = classLoader.getParent();
        }
    }
}

这里新建一个JavaClassLoaderDemo,循环打印其类加载器和父类加载器,控制台输出结果如下。

从结果可以看出ClassLoaderDemo类的类加载器是AppClassLoaderAppClassLoader的父类加载器是ExtClassLoader,而ExtClassLoader的父类加载器就为null了,这是因为ExtClassLoader的父类加载器BootstrapClassLoader是由C/C++语言实现的,所以在Java中无法获取到它的引用。接下来再进入源码来看一下,先看AppClassLoader

static class AppClassLoader extends URLClassLoader {
	......
}

查看源码发现AppClassLoader的父类并不是ExtClassLoader而是URLClassLoader,再看ExtClassLoader

static class ExtClassLoader extends URLClassLoader {
    ......
}

ExtClassLoader的父类也是URLClassLoader进而再看URLClassLoader

public class URLClassLoader extends SecureClassLoader implements Closeable {
	......
}

public class SecureClassLoader extends ClassLoader {
	......
}

public abstract class ClassLoader {
	......
}

URLClassLoader的父类是SecureClassLoader,而SecureClassLoader的父类是ClassLoaderClassLoader是一个抽象类。通过对源码的跟踪发现,似乎这里的继承关系与控制台输出的结果不太一致。于是进一步去看输出ClassLoaderDemo中调用的ClassLoader.getParent()方法源码。

   public final ClassLoader getParent() {
        if (parent == null)
            return null;
        SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            checkClassLoaderPermission(parent, Reflection.getCallerClass());
        }
        return parent;
    }
     // The parent class loader for delegation
    // Note: VM hardcoded the offset of this field, thus all new fields
    // must be added *after* it.
    private final ClassLoader parent;

ClassLoadergetParent方法中看到返回的是一个成员变量中的parent,他是一个ClassLoader类型对象。继续跟踪寻找它是在哪里初始化赋值的。

private ClassLoader(Void unused, ClassLoader parent) {
        this.parent = parent;
        if (ParallelLoaders.isRegistered(this.getClass())) {
            parallelLockMap = new ConcurrentHashMap<>();
            package2certs = new ConcurrentHashMap<>();
            domains =
                Collections.synchronizedSet(new HashSet<ProtectionDomain>());
            assertionLock = new Object();
        } else {
            // no finer-grained lock; lock on the classloader instance
            parallelLockMap = null;
            package2certs = new Hashtable<>();
            domains = new HashSet<>();
            assertionLock = this;
        }
    }

    protected ClassLoader(ClassLoader parent) {
        this(checkCreateClassLoader(), parent);
    }

    protected ClassLoader() {
        this(checkCreateClassLoader(), getSystemClassLoader());
    }

跟踪查看源码发现父类加载器parent是在ClassLoader的构造函数时传入的,如果没有传入默认调用getSystemClassLoader方法获取一个父类加载器。接下来继续查看getSystemClassLoader方法。

   public static ClassLoader getSystemClassLoader() {
        initSystemClassLoader();
        if (scl == null) {
            return null;
        }
        SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            checkClassLoaderPermission(scl, Reflection.getCallerClass());
        }
        return scl;
    }

getSystemClassLoader方法中又调用了initSystemClassLoader方法初始化系统类加载器,方法最后将这个类加载器scl返回。继续查看initSystemClassLoader方法。

private static synchronized void initSystemClassLoader() {
        if (!sclSet) {
            if (scl != null)
                throw new IllegalStateException("recursive invocation");
            sun.misc.Launcher l = sun.misc.Launcher.getLauncher();
            if (l != null) {
                Throwable oops = null;
                scl = l.getClassLoader();
      .......
}

这个方法中获取到Launcher之后调用了LaunchergetClassLoader方法获取到创建的类加载器,于是再到Launcher中查看。

public ClassLoader getClassLoader() {
        return this.loader;
}

LaunchergetClassLoader方法中返回了其成员变量中的loader对象,于是再去寻找这个对象的创建。

public Launcher() {
        Launcher.ExtClassLoader var1;
        try {
            var1 = Launcher.ExtClassLoader.getExtClassLoader();
        } catch (IOException var10) {
            throw new InternalError("Could not create extension class loader", var10);
        }

        try {
            this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
        } catch (IOException var9) {
            throw new InternalError("Could not create application class loader", var9);
        }
	......
}

Launcher的构造函数里找到,这里是通过Launcher.AppClassLoader.getAppClassLoader(var1)方法创建的loader,于是再进入查看。

public static ClassLoader getAppClassLoader(final ClassLoader var0) throws IOException {
            final String var1 = System.getProperty("java.class.path");
            final File[] var2 = var1 == null ? new File[0] : Launcher.getClassPath(var1);
            return (ClassLoader)AccessController.doPrivileged(new PrivilegedAction<Launcher.AppClassLoader>() {
                public Launcher.AppClassLoader run() {
                    URL[] var1x = var1 == null ? new URL[0] : Launcher.pathToURLs(var2);
                    return new Launcher.AppClassLoader(var1x, var0);
                }
            });
}

getAppClassLoader方法中最终new了一个AppClassLoader返回,这就又回到了AppClassLoader的构造方法。之前看过构造传入的第二个参数就是parent父类加载器,这里传入的var0可以看到就是ExtClassLoader类型。

总结来说ClassLoader的父子关系并不是由继承实现的,AB的父类加载器,并不表示B继承了A,每个CLassLoader中保存了一个它的父类加载器的引用,通过getParent方法获得的就是它的值,它是在类加载器创建时构造函数中进行赋值的。如果构造中没有传入父类加载器默认调用getSystemClassLoader方法获取系统类加载器,通过查看发现默认系统类加载器就是AppClassLoader。在Launcher的构造方法中,会依次创建ExtClassLoaderAppClassLoader,此时ExtClassLoader作为父类加载器由构造函数传入AppClassLoader。实际的类加载继承关系如下图。

Java类加载器

3.2 自定义类加载器

在一些特殊需求场景下可能需要程序员自定义类加载器。例如从网络下载一个加密过的.class类文件,此时就需要自定义类加载器,先进行文件解密再进行类加载。下面就来模拟一下这个例子,先定义一个测试类TestPrint

public class TestPrint {
    public void printString() {
        System.out.println("测试输出字符串");
    }
}

使用javac命令编译生成TestPrint.class文件。

再写个加密文件方法,这里加密就用简单使用下Base64加密,将编译生成的.class文件转成二进制字节流加密后再保存成本地文件。

public class Test {
    public static void main(String[] args) {
        byte[] classBytes = FileIOUtils.readFile2BytesByStream("/Users/sy/Downloads/ClassLoader/TestPrint.class");
        FileIOUtils.writeFileFromBytesByStream("/Users/sy/Downloads/ClassLoader/TestPrint.class",Base64.getEncoder().encode(classBytes));
}
}

得到加密后的TestPrint.class后接下来编写自定义的类加载器MyClassLoader

public class MyClassLoader extends ClassLoader {
    private String path;
    protected MyClassLoader(String path) {
        this.path = path;
    }
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        Class aClass = null;
        // 获取二进制字节流
        byte[] classBytes = loadClassBytes(name);
        if (classBytes == null) {
            System.out.println("class data is null");
        } else {
            aClass = defineClass(name, classBytes, 0, classBytes.length);
        }
        return aClass;
    }
    private byte[] loadClassBytes(String name) {
        String fileName = getFileName(name);
        File file = new File(path, fileName);
        InputStream inputStream = null;
        ByteArrayOutputStream outputStream = null;
        try {
            inputStream = new FileInputStream(file);
            outputStream = new ByteArrayOutputStream();
            byte[] buffer = new byte[1024];
            int len = 0;

            while ((len = inputStream.read(buffer)) != -1) {
                outputStream.write(buffer, 0, len);
            }
            return outputStream.toByteArray();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                inputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                outputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return null;
    }
    private String getFileName(String name) {
        String fileName;
        int index = name.lastIndexOf(".");
        if (index == -1) {
            fileName = name + ".class";
        } else {
            fileName = name.substring(index + 1) + ".class";
        }
        return fileName;
    }
}


自定义类加载器分为以下几个步骤:

  1. 继承抽象类ClassLoader
  2. 复写findClass方法。
  3. findClass方法中获取到二进制字节流后,调用defineClass方法。

MyClassLoaderfindClass方法中首先读取到本地硬盘下的TestPrint.class文件的字节流,然后调用defineClass方法,该方法会将字节流转化为Class类型。最后写一个测试类调用。

public class TestMyClassLoader {
    public static void main(String[] args) {
        // 初始化类加载器
        MyClassLoader myClassLoader = new MyClassLoader("/Users/sy/Downloads/ClassLoader");
        try {
            // 使用自定义类加载器获取Class
            Class<?> printTest = myClassLoader.loadClass("TestPrint");
            // 创建实例
            Object instance = printTest.newInstance();
            System.out.println("classloader:" + instance.getClass().getClassLoader());
            Method method = printTest.getDeclaredMethod("printString", null);
            method.setAccessible(true);
            method.invoke(instance, null);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

运行结果:

此时发生错误是因为做了Base64加密,需要在defineClass前进行一个解码操作,修改findClass方法。

@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
        Class aClass = null;
        // 获取二进制字节流
        byte[] classBytes = loadClassBytes(name);
        // Base64 decode
        classBytes = Base64.getDecoder().decode(classBytes);
        if (classBytes == null) {
            System.out.println("class data is null");
        } else {
            aClass = defineClass(name, classBytes, 0, classBytes.length);
        }
        return aClass;
}

再次运行查看结果:

此时程序就能正常的执行,调用类中的方法了。

4. 双亲委托

4.1 双亲委托模式

学习类加载器就避免不了要了解双亲委托,它是类加载器寻找加载类的模式。还是先看到之前自定义类加载器的例子,自定义时复写了findClass方法,但是使用时却没有直接调用这个方法,使用时是通过ClassLoader.loadClass方法获得Class的。双亲委托模式就是在这个方法中实现的,于是进入查看源码。

public Class<?> loadClass(String name) throws ClassNotFoundException {
        return loadClass(name, false);
}
protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // 首先检查该类是否加载过
            Class<?> c = findLoadedClass(name);
            // c为空说明没有加载过
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                	// 判断是否有父类加载器
                    if (parent != null) {
                      	// 有父类加载器就调用父类加载器的loadClass方法
                        c = parent.loadClass(name, false);
                    } else {
                    	// 没有父类加载器就调用这个方法
                    	// 方法中会调用native方法findBootstrapClass使用BootstrapClassLoader检查该类是否已加载
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }
			   // 走到此处c还为null说明父类加载器没有加载该类
                if (c == null) {
                    long t1 = System.nanoTime();
                    // 就调用自身的findClass查找加载该类
                    c = findClass(name);

                    // this is the defining class loader; record the stats
                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
}

loadClass中的代码逻辑非常清晰的描述了双亲委托模式,首先通过findLoadedClass方法检验要加载的类是否被加载过。加载过直接返回该类的Class对象,没加载过则c为空进入下面的判断,判断父类加载器parent是否为空,不为空调用父类的loadClass方法,为空则调用findBootstrapClassOrNull方法,该方法中会继续调用native方法findBootstrapClass使用BootstrapClassLoader检查该类是否已加载。接着如果没有加载该类就会调用自身的findClass方法查找加载该类。

最后对双亲委托模式做个总结:双亲委托模式是指类加载器加载一个类首先是判断这个类是否加载过,没加载过不会自己直接加载,而是委托给其父类加载器去查找,一直委托到顶层引导类加载器BootstrapClassLoader,如果BootstrapClassLoader找到该Class就会直接返回,没找到就会交给子类加载器依次向下查找,一直没找到最后就会交给自身去查找。

双亲委托模式

4.2 双亲委托模式的好处

双亲委托模式有两个好处:

  1. 避免了类的重复加载,一个类如果加载过一次就不需要再次加载了,而是直接读取已经加载的Class
  2. 类随着它的类加载器一起具备了一种带优先级的层次关系,使得更加安全。例如加载java.lang.Object类无论使用哪个类加载器加载,最终都会委托给顶层BootstrapClassLoader来加载,这样保证了Object类永远是同一个类,不会出现多个不同的Object类。这样也无法通过自定义一个Object类替换系统原来的Object类。

另外Java虚拟机判断两个类是同一个类,是依据两个类类名一致,并且被同一个类加载器加载。这里可以在之前的自定义类加载器的基础上测试下。将原来的MyClassLoader复制后重命名YourClassLoader一份,再编写一个测试类。

public class TestDifferentClassLoader {
    public static void main(String[] args) {
        MyClassLoader myClassLoader = new MyClassLoader("/Users/sy/Downloads/ClassLoader/");
        YourClassLoader yourClassLoader = new YourClassLoader("/Users/sy/Downloads/ClassLoader/");
        try {
            Class<?> myPrintTest = myClassLoader.loadClass("TestPrint");
            // 使用不同的类加载器
            Class<?> yourPrintTest = yourClassLoader.loadClass("TestPrint");

            Object myInstance = myPrintTest.newInstance();
            Object yourInstance = yourPrintTest.newInstance();

            System.out.println(myInstance.getClass().equals(yourInstance.getClass()));
            System.out.println(myInstance.getClass().getName());
            System.out.println(yourInstance.getClass().getName());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

运行结果:

修改一下用同一个类加载器。

public class TestDifferentClassLoader {
    public static void main(String[] args) {
        MyClassLoader myClassLoader = new MyClassLoader("D:\\");
        YourClassLoader yourClassLoader = new YourClassLoader("D:\\");
        try {
            Class<?> myPrintTest = myClassLoader.loadClass("TestPrint");
            // 用同一个类加载器
            Class<?> yourPrintTest = myClassLoader.loadClass("TestPrint");

            Object myInstance = myPrintTest.newInstance();
            Object yourInstance = yourPrintTest.newInstance();

            System.out.println(myInstance.getClass().equals(yourInstance.getClass()));
            System.out.println(myInstance.getClass().getName());
            System.out.println(yourInstance.getClass().getName());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

运行结果:

5. Android中类加载器

Android中类加载器与Java中的类加载器类似但是并不完全相同。Java中类加载器是加载.class文件,而Android中是加载的dex文件,不过Android中也分系统类加载器和自定义类加载器,系统类加载器主要包括以下三种:BootClassLoaderDexClassLoaderPathClassLoader。这里还是先循环打印一下Android中的父类加载器。

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        ClassLoader classLoader = MainActivity.class.getClassLoader();
        while (classLoader != null) {
            Log.d("classLoader", "name:" + classLoader);
            classLoader = classLoader.getParent();
        }
    }
}

运行日志结果:

从日志可以看到这里有两个类加载器,一个是BootClassLoader另一个是PathClassLoaderAndroid中的每个类加载器里同样保存一个父类加载器的引用,getParent方法获取到的同样是这个ClassLoader,还是先来看一下Android中的ClassLoader类。

public abstract class ClassLoader {
    private final ClassLoader parent;
		......
    public Class<?> loadClass(String name) throws ClassNotFoundException {
        return loadClass(name, false);
    }
		
    protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
            // 检查该类是否已经加载
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                try {
                    if (parent != null) {
                        // 父类加载器不为空则调用其的loadClass方法
                        c = parent.loadClass(name, false);
                    } else {
                    	// 父类加载器为空就调用findBootstrapClassOrNull方法
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }

                if (c == null) {
                    // 父类加载器没有查找到该类,则调用自身findClass方法查找加载
                    c = findClass(name);
                }
            }
            return c;
    }
    
    private Class<?> findBootstrapClassOrNull(String name)
    {
        return null;
    }
    public final ClassLoader getParent() {
        return parent;
    }
    ......
}

Android中的ClassLoader类同样是一个抽象类,它的loadClass方法中的逻辑和Java中的类似,同样是遵循了双亲委托模式,父类加载器不为空则调用父类加载器的loadClass方法,为空则调用findBootstrapClassOrNull方法,该方法这里直接返回的null。若父类加载器没有查找到需要加载的类,则调用自身的findClass方法。

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

接着看到ClassLoader中的findClass里是直接抛出了一个ClassNotFoundException异常,说明这个方法需要子类来实现。那么接下来就来看看Android中类加载器的实际继承关系。

抽象类ClassLoader定义了类加载器的主要功能,它的子类如上图。先根据源码梳理下它们之间的关系。

public class BaseDexClassLoader extends ClassLoader {
		......
}
public class SecureClassLoader extends ClassLoader {
		......
}
public class DexClassLoader extends BaseDexClassLoader {
		......
}
public class PathClassLoader extends BaseDexClassLoader {
		......
}
public final class InMemoryDexClassLoader extends BaseDexClassLoader {
  	......
}
public class URLClassLoader extends SecureClassLoader implements Closeable {
  	......
}
public final class DelegateLastClassLoader extends PathClassLoader {
  	......
}

除了这些子类,抽象类ClassLoader中还有一个内部类BootClassLoader

class BootClassLoader extends ClassLoader {
		......
}

综上所述Android中类加载器的继承关系如下图。

Android中类加载器

接下来简单了解一下这些类加载器。

5.1 BootClassLoader

class BootClassLoader extends ClassLoader {
    private static BootClassLoader instance;
    @FindBugsSuppressWarnings("DP_CREATE_CLASSLOADER_INSIDE_DO_PRIVILEGED")
    public static synchronized BootClassLoader getInstance() {
        if (instance == null) {
            instance = new BootClassLoader();
        }

        return instance;
    }
    public BootClassLoader() {
        super(null);
    }
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        return Class.classForName(name, false, null);
    }
		 ......
    @Override
    protected Class<?> loadClass(String className, boolean resolve)
           throws ClassNotFoundException {
        Class<?> clazz = findLoadedClass(className);

        if (clazz == null) {
            clazz = findClass(className);
        }
        return clazz;
    }
  	......
}

BootClassLoaderClassLoader的内部类继承自ClassLoader,并且提供了一个获取单例的getInstance方法。与Java中不同BootClassLoader不是用C/C++实现的是用Java实现的,Android系统启动时会使用BootClassLoader来预加载常用类。

5.2 DexClassLoader

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

DexClassLoader看名字就知道是用来加载dex文件的,它的构造函数中有四个参数:

  • dexPath:表示dex相关文件路径集合。
  • optimizedDirectory:表示解压的dex文件存储路径。
  • librarySearchPath:包含C/C++库的路径集合。
  • parent:父类加载器。

DexClassLoader继承自BaseDexClassLoader其主要的方法都在其父类中实现。

5.3 PathClassLoader

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

    public PathClassLoader(String dexPath, String librarySearchPath, ClassLoader parent) {
        super(dexPath, null, librarySearchPath, parent);
    }
}

PathClassLoader是用来加载系统类和应用程序的类,同样继承自BaseDexClassLoader类, 它的构造函数里没有optimizedDirectory参数,通常用来加载已经安装的dex文件。

5.4 BaseDexClassLoader

BaseDexClassLoaderDexClassLoaderPathClassLoader的父类。

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, null);

        if (reporter != null) {
            reporter.report(this.pathList.getDexPaths());
        }
    }
		......	
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        List<Throwable> suppressedExceptions = new ArrayList<Throwable>();
        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方法中是通过pathList.findClass方法获得的Class对象。

public Class<?> findClass(String name, List<Throwable> suppressed) {
        for (Element element : dexElements) {
            Class<?> clazz = element.findClass(name, definingContext, suppressed);
            if (clazz != null) {
                return clazz;
            }
        }

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

findClass方法中循环遍历了dexElements数组,再通过Element.findClass方法来获得Class对象,Element又是DexPathList的内部类,进一步找到它的findClass方法查看。

public Class<?> findClass(String name, ClassLoader definingContext,
                List<Throwable> suppressed) {
            return dexFile != null ? dexFile.loadClassBinaryName(name, definingContext, suppressed)
                    : null;
}

这个方法中又调用了DexFileloadClassBinaryName方法。

public Class loadClassBinaryName(String name, ClassLoader loader, List<Throwable> suppressed) {
        return defineClass(name, loader, mCookie, this, suppressed);
}

private static Class defineClass(String name, ClassLoader loader, Object cookie,
                                     DexFile dexFile, List<Throwable> suppressed) {
        Class result = null;
        try {
            result = defineClassNative(name, loader, cookie, dexFile);
        } catch (NoClassDefFoundError e) {
            if (suppressed != null) {
                suppressed.add(e);
            }
        } catch (ClassNotFoundException e) {
            if (suppressed != null) {
                suppressed.add(e);
            }
        }
        return result;
}

loadClassBinaryName方法里继续调用了defineClass方法,而defineClass方法里最终调用了defineClassNative这个native方法加载dex文件。

Android系统启动后init进程会启动Zygote进程,Zygote进程初始化时会预加载常用类,进而会调用Class.forName方法,该方法里会通过BootClassLoader.getInstance方法创建BootClassLoader。之后Zygote进程又会启动SystemServer进程,进而又会通过PathClassLoaderFactory创建PathClassLoader。总而言之,Android中的这两个类加载器在启动时就进行初始化创建了。

6. 总结

  • Java中类加载生命周期包括:加载、连接、初始化、使用、卸载这些阶段,其中连接阶段又分为验证、准备、解析。
  • Java中的 系统类加载器与Android中的系统类加载器并不相同,但都遵循了双亲委托模式。
  • 若有特殊需求需要自定义类加载器,可以通过继承ClassLoader类复写它的findClass方法实现自己的类加载器。

7. 参考资料

深入理解Java 虚拟机

Android进阶解密