Android类加载器ClassLoader
简介
Android从5.0开始采用ART虚拟机,通过读取dex字节码来加载,相比Jvm通过读取class字节码来加载,是一种更为优化的方案, 可以将多个.class文件合并成一个classes.dex文件
类关系图
Android的几种类加载器
ClassLoader classLoader = MainActivity.class.getClassLoader();
while (classLoader!=null) {
Log.e(TAG, "test: " + classLoader.toString());
//父类加载器,并不代表继承自父类加载器
classLoader=classLoader.getParent();
}
//运行结果
E/MainActivity: test: dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/com.example.component-ApyUv8YWIYq9y34yLqA8_A==/base.apk"],nativeLibraryDirectories=[/data/app/com.example.component-ApyUv8YWIYq9y34yLqA8_A==/lib/arm64, /system/lib64]]]
E/MainActivity: test: aaa@qq.com
核心类
- PathClassLoader
- DexClassLoader
- BaseDexClassLoader
- ClassLoader
- BootClassLoader
在PathClassLoader和DexClassLoader之间,在API level 26之前,DexClassLoader可以直接加载优化后的dex文件存在的目录,在API level 26之后已经取消了。
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);
}
}
ClassLoader
public abstract class ClassLoader {
private final ClassLoader parent;
public static ClassLoader getSystemClassLoader() {
return SystemClassLoader.loader;
}
static private class SystemClassLoader {
public static ClassLoader loader = ClassLoader.createSystemClassLoader();
}
private static ClassLoader createSystemClassLoader() {
String classPath = System.getProperty("java.class.path", ".");
String librarySearchPath = System.getProperty("java.library.path", "");
return new PathClassLoader(classPath, librarySearchPath,BootClassLoader.getInstance());
}
private ClassLoader(Void unused, ClassLoader parent) {
this.parent = parent;
}
protected ClassLoader() {
this(checkCreateClassLoader(), getSystemClassLoader());
}
}
getSystemClassLoader获取的loader对应的是PathClassLoader
BaseDexClassLoader
public class BaseDexClassLoader extends ClassLoader {
private final DexPathList pathList;
public BaseDexClassLoader(String dexPath,
String librarySearchPath, ClassLoader parent, ClassLoader[] sharedLibraryLoaders,
boolean isTrusted) {
super(parent);
this.pathList = new DexPathList(this, dexPath, librarySearchPath, null, isTrusted);
}
BaseDexClassLoader构造函数, 有一个非常重要的过程, 那就是初始化DexPathList对象
该构造函数的参数说明:
- dexPath: 包含目标类或资源的apk/jar列表;当有多个路径则采用:分割;
- optimizedDirectory: 优化后dex文件存在的目录, 可以为null;
- libraryPath: native库所在路径列表;当有多个路径则采用:分割;
- ClassLoader:父类的类加载器.
DexPathList
public final class DexPathList {
private Element[] dexElements;
private static final String DEX_SUFFIX = ".dex";
DexPathList(ClassLoader definingContext, String dexPath,
String librarySearchPath, File optimizedDirectory, boolean isTrusted) {
this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,
suppressedExceptions, definingContext, isTrusted);
}
private static Element[] makeDexElements(List<File> files, File optimizedDirectory,
List<IOException> suppressedExceptions, ClassLoader loader, boolean isTrusted) {
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()) {
// We support directories for looking up resources. Looking up resources in
// directories is useful for running libcore tests.
elements[elementsPos++] = new Element(file);
} else if (file.isFile()) {
String name = file.getName();
DexFile dex = null;
if (name.endsWith(DEX_SUFFIX)) {
// Raw dex file (not inside a zip/jar).
try {
dex = loadDexFile(file, optimizedDirectory, loader, elements);
if (dex != null) {
elements[elementsPos++] = new Element(dex, null);
}
} catch (IOException suppressed) {
System.logE("Unable to load dex file: " + file, suppressed);
suppressedExceptions.add(suppressed);
}
} else {
try {
dex = loadDexFile(file, optimizedDirectory, loader, elements);
} catch (IOException suppressed) {
/*
* IOException might get thrown "legitimately" by the DexFile constructor if
* the zip file turns out to be resource-only (that is, no classes.dex file
* in it).
* Let dex == null and hang on to the exception to add to the tea-leaves for
* when findClass returns null.
*/
suppressedExceptions.add(suppressed);
}
if (dex == null) {
elements[elementsPos++] = new Element(file);
} else {
elements[elementsPos++] = new Element(dex, file);
}
}
if (dex != null && isTrusted) {
dex.setTrusted();
}
} else {
System.logW("ClassLoader referenced unknown path: " + file);
}
}
if (elementsPos != elements.length) {
elements = Arrays.copyOf(elements, elementsPos);
}
return elements;
}
}
dex文件封装到Element
loadClass
public abstract class ClassLoader {
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;
}
protected Class<?> findClass(String name) throws ClassNotFoundException {
throw new ClassNotFoundException(name);
}
}
- 获取当前类加载类是否已经加载过指定类,如果加载过,则直接返回
- 调用parent的类加载递归加载该类,检测是否加载,若已加载则直接返回,否则继续执行
- 直到没有parent时,调用findBootstrapClassOrNull,检查是否加载,若已加载直接返回,否则继续执行
- 在向上寻找parent类加载器的过程中,如果还是没有找到,则调用当前的类加载器,通过findClass加载
向下查看子类BaseDexClassLoader中是否有findClass方法
public class BaseDexClassLoader extends ClassLoader {
private final DexPathList pathList;
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
Class c = pathList.findClass(name, suppressedExceptions);
return c;
}
}
最终调用pathlist的findClass
public final class DexPathList {
private Element[] dexElements;
public Class<?> findClass(String name, List<Throwable> suppressed) {
for (Element element : dexElements) {
Class<?> clazz = element.findClass(name, definingContext, suppressed);
if (clazz != null) {
return clazz;
}
}
return null;
}
}
最终调用Element的findClass
public final class DexPathList {
static class Element {
private final DexFile dexFile;
public Class<?> findClass(String name, ClassLoader definingContext,
List<Throwable> suppressed) {
return dexFile != null ? dexFile.loadClassBinaryName(name, definingContext, suppressed)
: null;
}
}
}
继续调用DexFile的loadClassBinaryName
public final class DexFile {
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;
}
private static native Class defineClassNative(String name, ClassLoader loader, Object cookie,
DexFile dexFile)
}
最终在native层创建目标类的对象并添加到虚拟机列表
总结
- PathClassLoader: 主要用于系统和app的类加载器,其中optimizedDirectory为null, 采用默认目录/data/dalvik-cache/
- BaseDexClassLoader: 比较基础的类加载器, PathClassLoader和DexClassLoader都只是在构造函数上对其简单封装而已.
- BootClassLoader: 作为父类的类构造器。
热修复核心逻辑:在DexPathList.findClass()过程,一个Classloader可以包含多个dex文件,每个dex文件被封装到一个Element对象,这些Element对象排列成有序的数组dexElements。当查找某个类时,会遍历所有的dex文件,如果找到则直接返回,不再继续遍历dexElements。也就是说当两个类不同的dex中出现,会优先处理排在前面的dex文件,这便是热修复的核心精髓,将需要修复的类所打包的dex文件插入到dexElements前面。
上一篇: 类加载器(ClassLoader)
下一篇: Xshell连接不上虚拟机