ClassLoader的主要作用就是加载类,且Android应用逻辑也是使用Java语言编写的,但Android ClassLoader和Java ClassLoader的原理却不太一样,关于Java 中ClassLoader的主要原理及使用见上一篇博文,本文主要比较两者的差别后,主要讨论Android ClassLoader的原理及使用。
Android ClassLoader & Java ClassLoader
Java中的ClassLoader可以加载jar文件和Class文件;但在Android中不同,Android打包后是apk应用,解压后看到其中包含一个class.dex文件,无论DVM还是ART他们加载的不再是Class文件,而是Dex文件,因此ClassLoader在加载类的过程中也略有不同。
ClassLoader的继承关系
其中,他们的关系如下:
- ClassLoader:抽象类,其中定义了ClassLoader的主要功能。BootClassLoader是它的内部类;
- SecureClassLoader: 和JDK8中的SecureClassLoader类的代码是一样的,它继承了抽象类ClassLoader。SecureClassLoader并不是ClassLoader的实现类,而是拓展了ClassLoader类加入了权限方面的功能,加强了ClassLoader的安全性;
- URLClassLoader:和JDK8中的URLClassLoader类的代码是一样的,它继承自SecureClassLoader,用来通过URl路径从jar文件和文件夹中加载类和资源;但由于dalvik不能直接识别jar,所以在Android中无法使用这个加载器;
- BaseDexClassLoader:继承自ClassLoader,是抽象类ClassLoader的具体实现类,PathClassLoader和DexClassLoader都继承它,他们的主要逻辑都在这里;
- InMemoryDexClassLoader:是Android8.0新增的类加载器,继承自BaseDexClassLoader,用于加载内存中的dex文件;
BootClassLoader
该类在libcore/ojluni/src/main/java/java/lang/ClassLoader.java
,与Java中的BootstrapClassLoader不同,他不是C/C++代码,而是Java代码,且是ClassLoader的内部类,且BootClassLoader是在Zygote进程的入口方法中创建的。(注:BootClassLoader的访问修饰符是默认的,只有在同一个包中才可以访问,因此我们无法使用)
PathClassLoader
该类在libcore/dalvik/src/main/java/dalvik/system/PathClassLoader.java
,用来加载系统类和应用程序的类,且不建议开发直接使用。代码如下:
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则是在Zygote进程创建SystemServer进程时创建的,其构造方法有三个参数:
- dexPath: 目标类所在的APK或jar文件的路径,多个路径用文件分隔符分隔,默认文件分隔符为‘:’。支持加载APK、DEX和JAR,也可以从SD卡进行加载,最后都会生成一个对应的dex文件。且最终将dexPath路径上的文件ODEX优化到内部位置optimizedDirectory,再进行加载;
- librarySearchPath: 包含 C/C++ 库的路径集合,多个路径用文件分隔符分隔分割,可以为null;
- parent: ClassLoader的parent;
DexClassLoader
该类在libcore/dalvik/src/main/java/dalvik/system/DexClassLoader.java
,可以加载dex文件以及包含dex的apk文件或jar文件,也支持从SD卡进行加载。因此可以在应用未安装的情况下加载dex相关文件。因此,它是热修复和插件化技术的基础。代码如下:
public class DexClassLoader extends BaseDexClassLoader {
public DexClassLoader(String dexPath, String optimizedDirectory,
String librarySearchPath, ClassLoader parent) {
super(dexPath, new File(optimizedDirectory), librarySearchPath, parent);
}
}
其中,参数optimizedDirectory表示存储ODEX文件的路径,ClassLoader只能加载内部存储路径中的dex文件,所以这个路径必须是一个内部存储路径。 应用程序第一次被加载的时候,为了提高以后的启动速度和执行效率,Android系统会对dex相关文件做一定程度的优化,并生成一个ODEX文件,此后再运行这个应用程序的时候,只要加载优化过的ODEX文件就行了,省去了每次都要优化的时间。
PathClassLoader没有参数optimizedDirectory,这是因为PathClassLoader已经默认了参数optimizedDirectory的路径为:/data/dalvik-cache
ClassLoader类加载过程
阅读源码可知,Android中ClassLoader加载类的过程也符合双亲委派机制。根据之前的分析可知,在查到对象的过程中,如果一直委托到顶层的父加载器依然找不到,则会调用findClass向下查找:
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) {
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.
c = findClass(name);
}
}
return c;
}
且PathClassLoader和DexClassLoader的实现中只有构造函数,其具体实现都在BaseDexClassLoader中,其部分源码为:
private final DexPathList pathList;
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
Class clazz = pathList.findClass(name);
if (clazz == null) {
throw new ClassNotFoundException(name);
}
return clazz;
}
DexPathList 中的 findClass:
public Class findClass(String name) {
for (Element element : dexElements) {
DexFile dex = element.dexFile;
if (dex != null) {
Class clazz = dex.loadClassBinaryName(name, definingContext);
if (clazz != null) {
return clazz;
}
}
}
return null;
}
DexFile 中的loadClassBinaryName:
public Class loadClassBinaryName(String name, ClassLoader loader) {
return defineClass(name, loader, mCookie);
}
private native static Class defineClass(String name, ClassLoader loader, int cookie);
因此,可得BaseDexClassLoader中有个pathList对象,pathList中包含一个DexFile的数组dexElements,类加载的过程即:遍历这个集合,通过DexFile去寻找,最终调用native方法的defineClass。
ClassLoader实例有多个
一个运行的Android应用至少有2个ClassLoader
- BootClassLoader:在Android系统启动的时候创建,用于加载系统Framework层级需要的类,而我们的Android应用也有可能需要用到一些系统的类,所以APP启动的时候也会将BootClassLoader传进来;
- 自己的ClassLoader实例:应用启动时创建,用于加载应用dex文件中的类;
Android ClassLoader动态加载可能遇到的问题
- 动态加载四大组件,需要提前在Manifest中注册;
- Resource资源问题,常见的是资源id问题;
网友评论