不管是android 还是java项目我们知道我们的java文件都会通过 javac命令编译成二进制class文件,然后虚拟机再通过classloader类对class文件进行加载生成Class对象。其中java 和 android的classloader实现上还是有区别的,java主要加载的是 .class 而 android加载的是.dex 文件。本篇主要分析android classloader源码,对java 只做大致介绍。
1. Classloader 作用 ?
一个程序在运行时候,我们的逻辑代码java文件都被编译成class文件,在我们程序的入口处调用其他class文件时候,由于java程序是动态加载的,如果其他class文件不存在,程序就会崩溃,这时候就需要把class文件通过类加载器加载到内存中去, 然后其他class文件才能使用当前被加载到内存的class文件,这就是classloader的作用。
2. Java 中的Classloader
java中有Bootstrap Classloader , Extensions ClassLoader和 App ClassLoader 。其中Bootstrap Classloader 是加载C/C++的类加载对象,而 Extensions ClassLoader和 App ClassLoader 是继承自 Classloader抽象类的,也是我们java代码使用的加载机制。(不做过多介绍)
3. Android 中的Classloader
首先在android中Classloader是个静态类,子类主要有 BaseDexClassloader,BootClassloader,PathClassloader和DexClassloader。继承关系如下图:
- Classloader 一个抽象类,让子类重写findClass() 方法,并且实现loadClass()方法双亲委托机制。
- BootClassloader 主要用于加载系统源码,例如:framework下的代码。
- BaseDexClassloader 加载dex文件的主要功能实现,内部包括 DexPathLIst类Element数组对象。
- DexClassloader 主要用于加载jar或者外部class文件。
- PathClassloader 主要用于加载已经安装到手机上的apk内部的dex。
整个Classloader架构核心的代码都在BaseDexClassloader中的 DexPathlist 类,下面就大致分析一下代码流程:
4. Anddrod Classloader 源码流程
- Classloader抽象类
在Classloader内部首先会对bootClassloader初始化/** * Encapsulates the set of parallel capable loader types. */ private static ClassLoader createSystemClassLoader() { //系统framework下文件路径 String classPath = System.getProperty("java.class.path", "."); // native c/c++ 的library包 String librarySearchPath = System.getProperty("java.library.path", ""); // TODO Make this a java.net.URLClassLoader once we have those? return new PathClassLoader(classPath, librarySearchPath, BootClassLoader.getInstance()); }
除了系统类加载器的创建,还有一个非常重要的方法代码
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; }
这段代码就是Classloader双亲委托机制的代码实现,下面会详细介绍 什么是双亲委托机制。
- DexClassloader
public class DexClassLoader extends BaseDexClassLoader { public DexClassLoader(String dexPath, String optimizedDirectory, String librarySearchPath, ClassLoader parent) { super((String)null, (File)null, (String)null, (ClassLoader)null); throw new RuntimeException("Stub!"); } }
dexPath : dex文件的路径,多个用 “:” 分开
optimizedDirectory : 解压的dex文件存储路径,这个路径必须是一个内部存储路径,一般情况下使用当前应用程序的私有路径:/data/data/<Package Name>/...
librarySearchPath :native 的library路径,可以为null
ClassLoader parent : 传入的父classloader,用于委托查询 - PathClassloader
public class PathClassLoader extends BaseDexClassLoader { public PathClassLoader(String dexPath, ClassLoader parent) { super((String)null, (File)null, (String)null, (ClassLoader)null); throw new RuntimeException("Stub!"); } public PathClassLoader(String dexPath, String librarySearchPath, ClassLoader parent) { super((String)null, (File)null, (String)null, (ClassLoader)null); throw new RuntimeException("Stub!"); } }
可以看到 PathClassloader 比较容易理解,与DexClassloader参数意思相同。
- BaseDexClassloader
最重要的base加载器,可以看到PathClassloader和DexClassloader都是继承自他的,所以几乎大部分逻辑都被放到了BaseClassloader中。private final DexPathList pathList; public BaseDexClassLoader(String dexPath, File optimizedDirectory, String librarySearchPath, ClassLoader parent, boolean isTrusted) { super(parent); this.pathList = new DexPathList(this, dexPath, librarySearchPath, null, isTrusted); if (reporter != null) { reportClassLoaderChain(); } }
经过分析BaseClassloader中的方法可以看到,BaseClassloader的功能几乎都是调用了 DexPathList 类,而且是在构造方法中初始化的。接下里主要分析DexPathList的结果。
-
DexPathList 结构
在DexPathList中保存了一个Element的数据结构,在构造方法中初始化Element数组private Element[] dexElements; DexPathList(ClassLoader definingContext, String dexPath, String librarySearchPath, File optimizedDirectory, boolean isTrusted) { this.definingContext = definingContext; .......... // save dexPath for BaseDexClassLoader this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory, suppressedExceptions, definingContext, isTrusted); // Native libraries may exist in both the system and // application library paths, and we use this search order: // // 1. This class loader's library path for application libraries (librarySearchPath): // 1.1. Native library directories // 1.2. Path to libraries in apk-files // 2. The VM's library path from the system property for system libraries // also known as java.library.path // // This order was reversed prior to Gingerbread; see http://b/2933456. ................. }
在这里根据传来的file路径,分割得到每个dex文件或者apk文件,然后放入到dexElements数组中去。看makeDexElement()方法
//待封装的 dex / apk文件数组 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) { if (file.isDirectory()) { // 是文件夹直接 加入到 数组中。 elements[elementsPos++] = new Element(file); } else if (file.isFile()) { String name = file.getName(); DexFile dex = null; // 是文件先判断 后缀名,然后把file 转换到 DexFile 数据结构 if (name.endsWith(DEX_SUFFIX)) { // Raw dex file (not inside a zip/jar). try { // 转换到 DexFile 数据结构 dex = loadDexFile(file, optimizedDirectory, loader, elements); if (dex != null) { elements[elementsPos++] = new Element(dex, null); } } catch (IOException suppressed) { } } else { dex = loadDexFile(file, optimizedDirectory, loader, elements); // 为 null 说明不是 dex 文件,仅仅是个文件 if (dex == null) { elements[elementsPos++] = new Element(file); } else { elements[elementsPos++] = new Element(dex, file); } } } return elements;
在这里把所有dex/apk等文件放入 element 数组中,等待 findClass()方法调用
5. 双亲委托机制
通过对 Classloader 中的loadClass()方法的代码分析,知道大致流程图
当我们有一个class文件需要加载时,首先会到我们自定义的MyClassloader中去查询,查不到就会委托内部持有的parent classloader(PathClassloader)中查询,如果查不到,在继续往上委托到boot classloader中查找。直到整个委托过程结束后,再从boot classloader 向 指定的 path 路径下搜索 class 文件,一次类推,直到Myclassloader 到指定路径下去查 class 文件,查到就 load 到内存(一般用 io 流加载),否则返回 null。
6. PathClassloader 的初始化
学过android系统启动流程,容易知道。当Zygote进程启动之后,就会创建SystemServer进程。在SystemServer进程被fork()之后,在通过反射调用 SystemServer 的 main() 方法 之前,传入了一个classloader,这个classloader就是 pathClassloader对象。
ClassLoader cl = null;
if (systemServerClasspath != null) {
cl = createPathClassLoader(systemServerClasspath, parsedArgs.targetSdkVersion);
Thread.currentThread().setContextClassLoader(cl);
}
/*
* Pass the remaining arguments to SystemServer.
*/
return ZygoteInit.zygoteInit(parsedArgs.targetSdkVersion, parsedArgs.remainingArgs, cl);
其中createPathClassloader()方法 返回了cl 对象。
public static ClassLoader createClassLoader(String dexPath,
String librarySearchPath, ClassLoader parent, String classloaderName) {
if (isPathClassLoaderName(classloaderName)) {
return new PathClassLoader(dexPath, librarySearchPath, parent);
} else if (isDelegateLastClassLoaderName(classloaderName)) {
return new DelegateLastClassLoader(dexPath, librarySearchPath, parent);
}
throw new AssertionError("Invalid classLoaderName: " + classloaderName);
}
加载流程
问题1.自己定义一个和系统包名一样的String类的时候,根本不会引用到我们自定义的String为什么? 因为string类在zygote进程启动的时候使用bootclassloader先把jdk等class先加载到内存了,等到我们app再用pathclassloader去加载string时候会委托到bootclassloader查询,然后就查询到了string对象。 问题2.为什么Tinker不使用DexClassLoader加载再反射获取它里面的Element?而是要反射makePathElement这样的方法把dex生成Element? 因为:就是A和B类,A调用了B,如果B被内联。那A和B要是同一个classloader加载的。如果A在补丁包,你用dexclassloader加载A,那不就不在同一个classloader了。
参考:
1. https://blog.csdn.net/xyang81/article/details/7292380
2. https://blog.csdn.net/itachi85/article/details/78088701
3. https://blog.csdn.net/itachi85/article/details/78276837
4. https://www.jianshu.com/p/96a72d1a7974
推荐: