不管是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
推荐:
https://www.diycode.cc/topics/321
转载:https://blog.csdn.net/WangRain1/article/details/103504590