Android 中各种ClassLoader的作用
本文概述:
- 此为android 虚拟机系列第二篇文章,文章重点探究了android 平台中的类加载器,涵盖类加载器作用,常见的类加载器,重要类加载器工作流程,双亲委派机制,对于部分重难点深入源码进行了细致剖析
大体描述:
-
ClassLoader 作用:加载类,联通真实的类文件与内存中使用的类
-
安卓中有哪些ClassLoader:查看继承关系
-
补充知识:Class
-
在Class 中有一个成员属性 classLoader
private transient ClassLoader classLoader; -
使用 .getClassLoader() 方法即可查看这个对象对应的类是由哪一个类加载器加载的
-
-
ClassLoader:继承关系
-
SecureClassLoader:安卓中用不到这个
-
BaseClassLoader
- PathClassLoader
- ImMemoryDexClassLoader:android 8.0后出现的
- DexClassLoader
-
BootClassLoader:用来加载android FrameWork层的类
-
android FrameWork层的类(手机内置的):String,Activity
- 使用 .getClassLoader() 方法即可查看这个对象对应的类是由哪一个类加载器加载的
-
AppCompatActivity 就不是,这个只是Google的一个官方类
- 由PathClassLoader 加载
-
-
-
-
PathClassLoader :安卓程序的类加载器
- 从上下文中调用getClassLoader,得到的是整个程序的类加载器(PathClassLoader)
-
这个跟BootClassLoader的区别
- BC:加载的是系统层的东西,PC加载的是用户层的东西
-
安卓中的类加载器针对于android 平台构建,但顶层的抽象类(ClassLoader ),大家都是一样的
-
Java 中加载 .class 流程
- 读取真实的 .class 文件,按照一定格式,解析成 JVM中的Class对象,然后使用
- 耗时 ---> 缓存机制
-
安卓中加载 .dex
- 解析成Class,然后使用
-
工作流程:PathClassLoader
- 需要使用这个类(实例化),通过程序的ClassLoader(PathClassLoader),调用loadClass方法
ClassLoader 加载流程与双亲委派机制
怎么去加载一个类?
//返回一个Class 对象
Class classObject = getClassLoader().loadClass("需要加载类的全类名")
PathClassLoader.loadClass() 在哪里?
-
这个里面是没有 loadClass方法的:查看其父类BaseDexClassLoader
-
BaseDexClassLoader 里面没有:再进入父类ClassLoader
-
这个里面就有了:有两个,有一个重载,查看关键的哪一个
源码分析:LoadClass
-
签名:传入全类名,加载一个类
protected Class<?> loadClass(String name, boolean resolve) -
寻找缓存:之前加载了的就不加载了,节省时间,缓存命中(return),没有命中进入if
Class<?> c = findLoadedClass(name);-
Java 中会 I/O(读取真实的 .class文件,耗时),然后解析
-
最终会调用到一个native方法:JIN层的C/C++
native static Class findLoadedClass(ClassLoader c1,String name);
-
-
进入 if :
-
拿到当前对象的父类加载器: BootClassLoader 不是这个类(PathCladdLoader)的父类(BaseClassLoader)
c = parent.loadClass(name, false);-
抽象类ClassLoader 存在类属性parent
private final ClassLoader parent;
-
-
执行流程:向上加载,双亲委派机制
-
假设当前对象的父类加载器,还有父类加载器,就一直向上调用
-
这里的parent 是ClassLoader的一个属性,并不是这个类的父类
-
比如,创建一个PathClassLoader
//classLoader:传入BootClassLoader(由系统调用的),赋值给PathClassLoader 的类属性 parent new PathClassLoader(String string,ClassLoader classLoader); -
为什么要设置成BootClassLoader
-
-
-
双亲委派机制
-
工作流程:先让父类加载器去加载,找不到自己再去找
-
细节:
-
避免重复:
- 如果类A已经被类加载器B加载了,那么从B 的缓存中去找 A的信息,这样就不用自己再去找一次,
- 如果说又加载了一次,引发了异常
-
数据结构:map<k,v>
- K:类加载器
- V:类的信息
-
加载问题:
- PathClassLoader 是由BootClassLoader加载
- BootClassLoader:Linux层加载的
-
整体流程:责任链设计模式
-
当类属性 parent存在,交给下面的去执行
-
补充:责任链设计模式的应用
- OkHttp 的五大拦截器、View的绘制流程、事件处理与分发
-
-
-
安全性考虑:
-
没有双亲委派机制:意味着不存在缓存
if (parent != null) { c = parent.loadClass(name, false); }-
当使用String类,找到的是用户自定义的,虽然包名相同但逻辑不同 ---> 相当于破坏系统源码了;
//用户自定义的代码 package java.lang; public class String{ …… }
-
-
当存在双亲委派机制:存在缓存
- String类的寻找,先从BootClassLoader找,发现有,那么直接用,不去加载用户自定义的String类;
-
那么用户自定义的类:例如MainActivity这些去哪里找
-
执行流程:
- 使用PathClassLoader 寻找程序的类(自定义的);但PathClassLoader 没有findClass 方法,进入其父类加载器BaseDexClassLoader ,通过里面的findClass 方法去找;
- BaseDexClassLoader.findClass 去调用DexPathList.findClass,因为DexPathList 存在element数组,其中的每一个元素相当于一个dex,此时去遍历这个数组,拿到dex之后去调用dexFile.loadClassBinaryName 去找
- 最终是调用到native方法:
private static native Class defineClassNative(String name,ClassLoader loader,Object cookie) throws ClassLoaderNotFoundException,NoClassDeffFoundError;
-
图示:
-
示意图:
-
具体分析过程:都是使用PathClassLoader加载,
//创建时会传入dex/Apk的地址:就是当前程序对应的文件 public PathClassLoader(String dexPath, ClassLoader parent) { super(dexPath, null, null, parent); } -
此时应该调用LoadClass 中自己找的部分
if (c == null) { // If still not found, then invoke findClass in order // to find the class. c = findClass(name); }- 调用 PathClassLoader,看其中有无findClass方法,没有,那么进入其父类BaseDexClassLoader,发现有
-
BaseDexClassLoader.findClass
-
执行流程:
- 查看共享文件中有没有
//这是一个:protected final ClassLoader[] sharedLibraryLoaders; 在BaseDexClassLoader构造时初始化 public BaseDexClassLoader(String dexPath, String librarySearchPath, ClassLoader parent, ClassLoader[] libraries) if (sharedLibraryLoaders != null) { ……- 找类
Class c = pathList.findClass(name, suppressedExceptions);-
此时调用DexPathList.findClass 去遍历element数组
//去拿到每一个element,再拿到里面的dexFile,就是dex文件了 for (Element element : dexElements) {-
拿具体的dex 文件,找不到就返回null,后面就抛出异常了
Class<?> clazz = element.findClass(name, definingContext, suppressed);
-
-
问题:
-
dex 文件是哪里来的?
- APK里面的
-
Element 数组中有多少个元素
- Apk 中有多少个dex文件,那么就有多少个元素
-
ClassLoader 是怎么跟文件关联的:通过传参(将dex 的地址传进来)
public PathClassLoader(String dexPath, ClassLoader parent) {
super(dexPath, null, null, parent);
}
-
操作与对应的配置文件:
- 操作jar 包:jarFile
- 操作dex 文件:dexFile
- PathList 是什么
private final DexPathList pathList;
-
在BaseDexClassLoader 构造中实例化
- 传入需要加载的应用(jar)的dex/apk地址
public BaseDexClassLoader(String dexPath,
String librarySearchPath, ClassLoader parent, ClassLoader[] sharedLibraryLoaders,
boolean isTrusted) {
super(parent);
this.pathList = new DexPathList(this, dexPath, librarySearchPath, null, isTrusted);
}
-
DexPathList 干了什么?
- 判空,抛出空指针异常
if (definingContext == null) { throw new NullPointerException("definingContext == null"); }- 根据dexPath:传入应用对应的APK文件
this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory, suppressedExceptions, definingContext, isTrusted);- 最终封装成element数组:
//一个dex,一个element对象,可能存在多个,那就搞一个数组 private Element[] dexElements;-
PathClassLoader 为什么可以处理多个dex文件:splitDexPath 的作用:进行分组、拆组
-
比如目录A里面a.dex 与 b.dex,那么就传入下面这样的字符串
/a/a.dex:/a/d.dex -
根据 : 分割出一个List 集合,一个冒号,集合中两个item属性
-
-
findClass原代码
protected Class<?> findClass(String name) throws ClassNotFoundException { // First, check whether the class is present in our shared libraries. if (sharedLibraryLoaders != null) { for (ClassLoader loader : sharedLibraryLoaders) { try { return loader.loadClass(name); } catch (ClassNotFoundException ignored) { } } } // Check whether the class in question is present in the dexPath that // this classloader operates on. List<Throwable> suppressedExceptions = new ArrayList<Throwable>(); Class c = pathList.findClass(name, suppressedExceptions); if (c == null) { ClassNotFoundException cnfe = new ClassNotFoundException( "Didn't find class "" + name + "" on path: " + pathList); for (Throwable t : suppressedExceptions) { cnfe.addSuppressed(t); } throw cnfe; } return c; }