话题:Android中的ClassLoader
1、Android中有哪几种ClassLoader?它们的作用和区别是什么?
2、简述ClassLoader的双亲委托模型
3、简述双亲委托模型在热修复领域的应用
0、什么是ClassLoader
要深入了解ClassLoader,首先就要知道ClassLoader是用来干什么的,顾名思义,它就是用来加载Class文件到JVM,以供程序使用 的。我们知道,java程序可以动态加载类定义,而这个动态加载的机制就是通过ClassLoader来实现的,所以可想而知ClassLoader的重要性如何。
看到这里,可能有的朋友会想到一个问题,那就是既然ClassLoader是用来加载类到JVM中的,那么ClassLoader又是如何被加载呢?难道它不是java的类?
没有错,在这里确实有一个ClassLoader不是用java语言所编写的,而是JVM实现的一部分,这个ClassLoader就是 bootstrap classloader(启动类加载器),这个ClassLoader在JVM运行的时候加载java核心的API以满足java程序最基本的需求,其中 就包括用户定义的ClassLoader,这里所谓的用户定义是指通过java程序实现的ClassLoader,一个是ExtClassLoader, 这个ClassLoader是用来加载java的扩展API的,也就是/lib/ext中的类,一个是AppClassLoader,这个 ClassLoader是用来加载用户机器上CLASSPATH设置目录中的Class的,通常在没有指定ClassLoader的情况下,程序员自定义 的类就由该ClassLoader进行加载。
当运行一个程序的时候,JVM启动,运行bootstrap classloader,该ClassLoader加载java核心API(ExtClassLoader和AppClassLoader也在此时被加 载),然后调用ExtClassLoader加载扩展API,最后AppClassLoader加载CLASSPATH目录下定义的Class,这就是一 个程序最基本的加载流程。
1.1、Android有几种ClassLoader
ClassLoader是个抽象类,有2个直接关系的子类:BaseDexClassLoader,SecureClassLoader。
有5个间接关系的子类:DelegateLastClassLoader,DexClassLoader,InMemoryDexClassLoader,PathClassLoader,URLClassLoader。
关系图如下所示:
1.2、它们的作用和区别是什么?
我们先来看下他们的官方定义
SecureClassLoader
This class extends ClassLoader with additional support for defining classes with an associated code source and permissions which are retrieved by the system policy by default.
SecureClassLoader继承ClassLoader,并提供了对定义具有关联代码源和权限的类的额外支持,默认情况下,系统策略将检索这些类。
URLClassLoader
This class loader is used to load classes and resources from a search path of URLs referring to both JAR files and directories.
URLClassLoader继承自SecureClassLoader,这个类加载器用于从URL搜索路径加载类和资源,这些URL引用JAR文件和目录。
BaseDexClassLoader
Base class for common functionality between various dex-based ClassLoader implementations.
各种基于Dex的ClassLoader实现之间的通用功能的基类。
DexClassLoader
A class loader that loads classes from .jar and .apk files containing a classes.dex entry. This can be used to execute code not installed as part of an application.
DexClassLoader继承自BaseDexClassLoader,支持加载包含classes.dex e的jar和apk文件。它可以用于执行那些未被作为App一部分的代码
InMemoryDexClassLoader
A ClassLoader implementation that loads classes from a buffer containing a DEX file. This can be used to execute code that has not been written to the local file system.
InMemoryDexClassLoader继承自BaseDexClassLoader,它可以从包含DEX文件的缓冲区加载类。这可以用于执行尚未写入本地文件系统的代码。
PathClassLoader
Provides a simple 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).
PathClassLoader继承自BaseDexClassLoader,它提供一个简单的ClassLoader实现,对本地文件系统中的文件和目录列表进行操作,但不尝试从网络加载类。Android将这个类用于其系统类加载程序和应用程序类加载程序。
DelegateLastClassLoader
A ClassLoader implementation that implements a delegate last lookup policy. For every class or resource this loader is requested to load, the following lookup order is employed:
The boot classpath is always searched first
Then, the list of dex files associated with this classloaders's dexPath is searched.
Finally, this classloader will delegate to the specified parent.
DelegateLastClassLoader继承自PathClassLoader,是API27新增的加载器。
DelegateLastClassLoader实行最后查找策咯。使用DelegateLastClassLoader来加载每个类或资源时,使用以下查找顺序:
首先,判断是否已经加载过这个类
然乎,搜索此类的类加载器是否加载过该类
最后,此类类加载器委托给指定的父加载对象。
PathClassLoader与DexClassLoader的区别
- PathClassLoader:只能加载已经安装到Android系统中的apk文件(/data/app目录),是Android默认使用的类加载器。
- DexClassLoader:可以加载任意目录下的dex/jar/apk/zip文件,比PathClassLoader更灵活,是实现热修复的重点。
2、简述ClassLoader的双亲委托模型
某个特定的类加载器在接到加载类的请求时,首先首先检查该name指定的class是否有被加载,如果已经加载过了,返回这个类,没有加载过时将加载任务委托给父类加载器,依次递归,如果父类加载器可以完成类加载任务,就成功返回。只有父类加载器无法完成此加载任务时,才自己去加载。
我们来看一下ClassLoader中的一段源代码:
protected synchronized Class loadClass(String name, boolean resolve) throws ClassNotFoundException{
// 首先检查该name指定的class是否有被加载
Class c = findLoadedClass(name);
if (c == null) {
try {
if (parent != null) {
//如果parent不为null,则调用parent的loadClass进行加载
c = parent.loadClass(name, false);
}else{
//parent为null,则调用BootstrapClassLoader进行加载
c = findBootstrapClass0(name);
}
}catch(ClassNotFoundException e) {
//如果仍然无法加载成功,则调用自身的findClass进行加载
c = findClass(name);
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
3、简述双亲委托模型在热修复领域的应用
我们已经知道当类加载器无法加载这类的时候,会调用自身的findClass进行加载:
public Class findClass(String name, List suppressed) {
// 遍历 dexElements 数组,依次寻找对应的 class,一旦找到就终止遍历
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;
}
那么要实现热修复实现的一个点,就是将补丁dex文件放到dexElements数组前面,这样在加载class时,优先找到补丁包中的dex文件,加载到 class之后就不再寻找,从而原来的apk 文件中同名的有bug类就不会再使用,从而达到修复的目的。