#Android#
概述
Android与JVM加载机制相同点:
- 都需要使用到ClassLoader将目标类加载到内存
Android与JVM加载机制差异点:
- Jvm主要读取class字节码
- art主要读取dex字节码, 这是更好的方案, 可以将多个class文件合并成一个classes.dex文件
关系类图
类图分析
类图学习: UML类图
- ClassLoader是一父类, 继承它的类有: BootClassLoader, BaseDexClassLoader, WarningContextClassLoader
- BaseDexClassLoader与DexPathList类之间是组合关系, BaseDexClassLoader是拥有者, 同时当BaseDexClassLoader生命期结束时, 所属DexPathList对象的生命期也结束.
- BaseDexClassLoader是ClassLoader的子类, 同时也有子类: DexClassLoader, PathClassLoader
五种构造器
照层次结构查看各个构造器
PathClassLoader
父加载器是BaseDexClassLoader
注意传给父加载器BaseDexClassLoader的optimizedDirectory参数为null
public class PathClassLoader extends BaseDexClassLoader {
public PathClassLoader(String dexPath, ClassLoader parent) { // 构造方法: 需要传入父加载器parent, 还有需要加载的dex路径
super(dexPath, null, null, parent); // 直接传递给父加载器(跟JVM的好像不太一样??, 没有先判断该类是否已加载到内存, 直接就传给父加载器了. 也可能还没有实现)
// 注意传给父加载器BaseDexClassLoader的optimizedDirectory参数为null
}
public PathClassLoader(String dexPath, String libraryPath,
ClassLoader parent) { // 另一个构造方法, 多了一个libraryPath, 应该时运行库的路径
super(dexPath, null, libraryPath, parent);
}
}
DexClassLoader
父加载器也是BaseDexClassLoader
与PathClassLoader相比, 多传入了optimizedDirectory参数
public class DexClassLoader extends BaseDexClassLoader {
public DexClassLoader(String dexPath, String optimizedDirectory,
String libraryPath, ClassLoader parent) { // 又多了一个优化目录optimizedDirectory参数
super(dexPath, new File(optimizedDirectory), libraryPath, parent);
}
}
BaseDexClassLoader
简要说明
- 是PathClassLoader和DexClassLoader的父类
- 其父类是ClassLoader
- 有一个非常重要的步骤: 初始化了DexPathList对象 (在上面的类图中也出现了, 与BaseDexClassLoader是组合关系)
参数说明
- dexPath: 包含目标类或资源的apk/jar列表, 当有多个路径时使用":"分割
- optimizedDirectory: 优化后dex文件存在的目录, 可以为null
- libraryPath: native库所在路径列表, 多个路径使用":"分割
- ClassLoader: 父加载器
public class BaseDexClassLoader extends ClassLoader {
private final DexPathList pathList; //记录dex文件路径信息
public BaseDexClassLoader(String dexPath, File optimizedDirectory, String libraryPath, ClassLoader parent) { // 参数列表与DexClassLoader的构造方法一样
super(parent); // 直接传给父加载器
this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory);
}
}
ClassLoader (抽象类)
注意的点:
- ClassLoader无参构造方法, 默认将SystemClassLoader作为父加载器
- 第三个构造方法的boolean参数表示是否允许父加载器为空, 如果false且父加载器为空, 则抛出异常
public abstract class ClassLoader {
private ClassLoader parent; //记录父类加载器
protected ClassLoader() { // 无参构造方法, 默认将父加载器设置为SystemClassLoader
this(getSystemClassLoader(), false); //调用重载构造方法, 见下文
}
protected ClassLoader(ClassLoader parentLoader) {
this(parentLoader, false);
}
ClassLoader(ClassLoader parentLoader, boolean nullAllowed) { // 重载构造方法
if (parentLoader == null && !nullAllowed) {
//父类的类加载器为空,则抛出异常
throw new NullPointerException("parentLoader == null && !nullAllowed");
}
parent = parentLoader;
}
}
SystemClassLoader
上面的getSystemClassLoader()
方法返回的是PathClassLoader (一般自定义的ClassLoader继承了ClassLoader类, 然后属于最底层的ClassLoader, 默认以PathClassLoader作为父加载器的话就不奇怪了)
public abstract class ClassLoader {
static private class SystemClassLoader {
public static ClassLoader loader = ClassLoader.createSystemClassLoader();
}
public static ClassLoader getSystemClassLoader() {
return SystemClassLoader.loader;
}
private static ClassLoader createSystemClassLoader() {
//此处classPath默认值为"."
String classPath = System.getProperty("java.class.path", ".");
// BootClassLoader见小节2.5
return new PathClassLoader(classPath, BootClassLoader.getInstance());
}
}
BootClassLoader
注意的点:
- 无参构造方法中: 传入的是null 和 true, 表示父加载器待定
class BootClassLoader extends ClassLoader {
private static BootClassLoader instance;
public static synchronized BootClassLoader getInstance() {
if (instance == null) {
instance = new BootClassLoader();
}
return instance;
}
public BootClassLoader() { // 无参构造方法
super(null, true);
} }
PathClassLoader加载类的过程
PathClassLoader加载类过程是一个实例, 来简述ClassLoader的具体加载过程
大致流程:
- 初始化
- 执行
loadClass()
方法加载相应的类 (当然需要先双亲委派, 这里主要讲加载流程, 这个就不细讲了)
初始化
public class PathClassLoader extends BaseDexClassLoader {
public PathClassLoader(String dexPath, ClassLoader parent) {
super(dexPath, null, null, parent); //见下文
}
}
public class BaseDexClassLoader extends ClassLoader {
private final DexPathList pathList;
public BaseDexClassLoader(String dexPath, File optimizedDirectory,
String libraryPath, ClassLoader parent) {
super(parent); //见下文
//收集dex文件和Native动态库【见小节3.2】
this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory);
}
}
public abstract class ClassLoader {
private ClassLoader parent; //父类加载器
protected ClassLoader(ClassLoader parentLoader) {
this(parentLoader, false);
}
ClassLoader(ClassLoader parentLoader, boolean nullAllowed) {
parent = parentLoader;
}
}
大致流程
DexPathList
前提:
- BaseDexClassLoader的参数说明
- dexElement: 根据":"分隔符, 将dexpath转换成File列表, 记录所有的dexFile
- nativeLibraryPathElement: 同理, 记录所有的nativeLibrary的File列表
final class DexPathList {
private Element[] dexElements;
private final List<File> nativeLibraryDirectories; // 文件对象List, 存储native动态库目录(Directories)
private final List<File> systemNativeLibraryDirectories; // 文件对象List, 存储系统native动态库目录(Directories)
final class DexPathList {
public DexPathList(ClassLoader definingContext, String dexPath,
String libraryPath, File optimizedDirectory) { // definingContext 定义上下文
...
this.definingContext = definingContext; // context数据域赋值
ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>(); // 被抑制的异常List
//记录所有的dexFile文件【小节3.2.1】
this.dexElements = makePathElements(splitDexPath(dexPath), optimizedDirectory, suppressedExceptions); // 可能存在多条路径(使用":"分割), 将多个dex分为一个数组
//app目录的native库
this.nativeLibraryDirectories = splitPaths(libraryPath, false); // native库也可能存在多条路径(使用":"分割)
//系统目录的native库
this.systemNativeLibraryDirectories = splitPaths(System.getProperty("java.library.path"), true); // 同上获取系统native库
List<File> allNativeLibraryDirectories = new ArrayList<>(nativeLibraryDirectories);
allNativeLibraryDirectories.addAll(systemNativeLibraryDirectories); // 将本地的和系统的native库整合在一起
//记录所有的Native动态库
this.nativeLibraryPathElements = makePathElements(allNativeLibraryDirectories, null, suppressedExceptions); // 同上获取Element数组
...
}
}
makePathElements()
在记录dexElements时用到, 继续跟进makeDexElements()
private static Element[] makePathElements(List<File> files, File optimizedDirectory,
List<IOException> suppressedExceptions) {
return makeDexElements(files, optimizedDirectory, suppressedExceptions, null);
}
makeDexElements()
作用
根据File列表, 创建Element数组
大致流程
-
创建Elements数组
-
循环获取File对象
-
判断是否是目录
- 如果是目录则创建一个根据File对象创建一个Element对象, 放入elements中
-
判断是否是文件
-
获取文件名
-
创建一个DexFile对象
-
如果是dex文件
-
调用loadDexFile()加载dex文件
-
如果加载成功
- 根据DexFile对象创建一个Element对象, 放入elements中
-
-
如果不是dex文件
-
调用loadDexFile()加载dex文件
-
如果加载成功
- 根据DexFile对象和File对象创建一个Element对象, 放入elements中
-
如果加载失败
- 根据File对象创建一个Element对象, 放入elements中
-
-
-
既不是目录, 也不文件
- 输出错误提示
-
-
如果elementPos的长度与预先申请的长度不一致(说明路径中有错误 或者 loadDexFile()发生错误)
- 使用Arrays.copyof拷贝合适长度的elements返回
-
elements返回
private static Element[] makeDexElements(List<File> files, File optimizedDirectory,
List<IOException> suppressedExceptions, ClassLoader loader) {
return makeDexElements(files, optimizedDirectory, suppressedExceptions, loader, false);
}
// 重载方法, 多了一个boolean参数isTrusted, 默认为false
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; // 下标
for (File file : files) {
if (file.isDirectory()) { // 如果是目录
elements[elementsPos++] = new Element(file); // 根据File对象, 创建一个Element对象
} else if (file.isFile()) { // 如果是文件
String name = file.getName(); // 获取文件名
DexFile dex = null; // 创建一个DexFile对象
//匹配以.dex为后缀的文件
if (name.endsWith(DEX_SUFFIX)) { // 如果是dex文件
//【小节3.2.3】
dex = loadDexFile(file, optimizedDirectory, loader, elements); // 调用loadDexFile()加载dex文件
if (dex != null) { // 若加载成功
elements[elementsPos++] = new Element(dex, null); // 将dexFile对象添加到Element数组中
}
} else { // 如果不是dex文件
dex = loadDexFile(file, optimizedDirectory, loader, elements); // 同样调用loadDexFile()加载文件
if (dex == null) { // 如果加载失败
elements[elementsPos++] = new Element(file); // 则直接添加File对象
} else {
elements[elementsPos++] = new Element(dex, file); // 否则调用另一个构造方法构造Element对象
}
}
if (dex != null && isTrusted) { // 设置dex文件是否可信任
dex.setTrusted();
}
} else { // 既不是目录, 也不是文件, 则输出错误消息
System.logW("ClassLoader referenced unknown path: " + file);
}
}
if (elementsPos != elements.length) { // 如果elementPos的长度与预先申请的长度不一致(说明路径中有错误)
elements = Arrays.copyOf(elements, elementsPos); // 则创建合适长度的elements返回
}
return elements;
}
loadDexFile()
作用
根据File对象, 创建DexFile对象
private static DexFile loadDexFile(File file, File optimizedDirectory, ClassLoader loader,
Element[] elements)
throws IOException {
if (optimizedDirectory == null) { // 如果优化目录为null
return new DexFile(file, loader, elements); //创建DexFile对象
} else { // 如果有优化目录
String optimizedPath = optimizedPathFor(file, optimizedDirectory);
return DexFile.loadDex(file.getPath(), optimizedPath, 0, loader, elements);
}
}
DexFile
作用
DexFile类
DexFile(File file, ClassLoader loader, DexPathList.Element[] elements)
throws IOException {
this(file.getPath(), loader, elements);
}
DexFile(String fileName, ClassLoader loader, DexPathList.Element[] elements) throws IOException {
// 初始化三个数据域
mCookie = openDexFile(fileName, null, 0, loader, elements);
mInternalCookie = mCookie;
mFileName = fileName;
}
OpenDexFile
作用
打开dex文件, 并返回Object对象
参数说明
- sourceName: PathClassLoader构造函数传递的dexPath中用":"后的文件名, 就是单个dex文件路径
- outputName: null
- flags: 0
- loader: null
- elements: 前面的Element数组
private static Object openDexFile(String sourceName, String outputName, int flags,
ClassLoader loader, DexPathList.Element[] elements) throws IOException {
//【见小节3.2.6】
return openDexFileNative(new File(sourceName).getAbsolutePath(),
(outputName == null) ? null : new File(outputName).getAbsolutePath(),
flags,
loader,
elements);
}
DexFile_openDexFileNative
static jobject DexFile_openDexFileNative(JNIEnv* env,
jclass,
jstring javaSourceName,
jstring javaOutputName ATTRIBUTE_UNUSED,
jint flags ATTRIBUTE_UNUSED,
jobject class_loader,
jobjectArray dex_elements) {
ScopedUtfChars sourceName(env, javaSourceName);
if (sourceName.c_str() == nullptr) {
return 0;
}
Runtime* const runtime = Runtime::Current();
ClassLinker* linker = runtime->GetClassLinker();
std::vector<std::unique_ptr<const DexFile>> dex_files;
std::vector<std::string> error_msgs;
const OatFile* oat_file = nullptr;
dex_files = runtime->GetOatFileManager().OpenDexFilesFromOat(sourceName.c_str(),
class_loader,
dex_elements,
/*out*/ &oat_file,
/*out*/ &error_msgs);
if (!dex_files.empty()) {
jlongArray array = ConvertDexFilesToJavaArray(env, oat_file, dex_files);
...
return array;
} else {
...
return nullptr;
}
}
初始化结果
PathClassLoader创建完成后, 拥有:
- 目标程序的文件路径
- native lib路径
- 父加载器对象
loadClass
PathClassLoader完成初始化后, 即可开始loadClass(), 该方法继承自ClassLoader类中
ClassLoader
public abstract class ClassLoader {
public Class<?> loadClass(String className) throws ClassNotFoundException {
return loadClass(className, false); // 调用重载方法
}
// 重载方法
protected Class<?> loadClass(String className, boolean resolve) throws ClassNotFoundException {
//判断当前类加载器是否已经加载过指定类,若已加载则直接返回
Class<?> clazz = findLoadedClass(className);
if (clazz == null) {
//如果没有加载过,则调用parent的类加载递归加载该类,若已加载则直接返回
clazz = parent.loadClass(className, false);
if (clazz == null) {
//还没加载,则调用当前类加载器来加载[见小节3.2]
clazz = findClass(className);
}
}
return clazz;
}
}
findLoadedClass()
作用
在该加载器中, 判断该类是否已加载
protected final Class<?> findLoadedClass(String name) {
ClassLoader loader;
if (this == BootClassLoader.getInstance()) // 如果是BootClassLoader
loader = null; // loader数据域为null
else
loader = this; // 否则为自身
return VMClassLoader.findLoadedClass(loader, name); // 调用VMClassLoader.findLoadedClass()
}
findClass()
作用
调用了BaseDexClassLoader的pathList.findClass()来找到类
public class BaseDexClassLoader extends ClassLoader {
protected Class<?> findClass(String name) throws ClassNotFoundException {
Class c = pathList.findClass(name, suppressedExceptions);
...
return c;
}
}
DaxPathList.findClass()
作用
一个ClassLoader可以包含多个dex文件, 每个dex文件被封装到一个Element对象, 循环查询DaxPathList中dex文件的elements数组, 如果找到了该类, 则直接退出, 不再继续遍历.(热修复的核心, 只需要将修复的类打包到dex文件中, 然后插入到之前的类所在dex文件之前, 就会优先加载该dex文件中的类.)
再调用loadClassBinaryName()根据类名称来加载Class
public Class findClass(String name, List<Throwable> suppressed) {
for (Element element : dexElements) { // dex文件的elements
DexFile dex = element.dexFile; // 获取DexFile
if (dex != null) {
//找到目标类,则直接返回[见小节3.6]
Class clazz = dex.loadClassBinaryName(name, definingContext, suppressed); // 调用loadClassBinaryName()方法加载类
if (clazz != null) {
return clazz;
}
}
}
return null;
}
DexFile.laodClassBinaryName()
public final class DexFile {
public Class loadClassBinaryName(String name, ClassLoader loader, List<Throwable> suppressed) {
return defineClass(name, loader, mCookie, suppressed);
}
private static Class defineClass(String name, ClassLoader loader, Object cookie, List<Throwable> suppressed) {
Class result = null;
// 调用
try {
result = defineClassNative(name, loader, cookie);
} catch (NoClassDefFoundError e) {
if (suppressed != null) {
suppressed.add(e);
}
} catch (ClassNotFoundException e) {
if (suppressed != null) {
suppressed.add(e);
}
}
return result;
}
}
defineClassNative()
static jclass DexFile_defineClassNative(JNIEnv* env, jclass, jstring javaName, jobject javaLoader,
jobject cookie) {
std::unique_ptr<std::vector<const DexFile*>> dex_files = ConvertJavaArrayToNative(env, cookie);
if (dex_files.get() == nullptr) {
return nullptr; //dex文件为空, 则直接返回
}
ScopedUtfChars class_name(env, javaName);
if (class_name.c_str() == nullptr) {
return nullptr; //类名为空, 则直接返回
}
const std::string descriptor(DotToDescriptor(class_name.c_str()));
const size_t hash(ComputeModifiedUtf8Hash(descriptor.c_str())); //将类名转换为hash码
for (auto& dex_file : *dex_files) {
const DexFile::ClassDef* dex_class_def = dex_file->FindClassDef(descriptor.c_str(), hash);
if (dex_class_def != nullptr) {
ScopedObjectAccess soa(env);
ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
class_linker->RegisterDexFile(*dex_file);
StackHandleScope<1> hs(soa.Self());
Handle<mirror::ClassLoader> class_loader(
hs.NewHandle(soa.Decode<mirror::ClassLoader*>(javaLoader)));
//获取目标类
mirror::Class* result = class_linker->DefineClass(soa.Self(), descriptor.c_str(), hash,
class_loader, *dex_file, *dex_class_def);
if (result != nullptr) {
// 找到目标对象
return soa.AddLocalReference<jclass>(result);
}
}
}
return nullptr; //没有找到目标类
}
总结
各加载器职责
-
PathClassLoader
- 系统类加载器
- app类加载器
- optimizedDirectory为null, 采用默认目录/data/dalvik-cache/
-
DexClassLoader
- 从包含classes.dex的jar或者apk中, 加载类
- 可用于执行动态加载,但必须是app私有可写目录来缓存odex文件.
- 能够加载系统没有安装的apk或者jar文件, 因此很多插件化方案都是采用DexClassLoade
-
BaseDexClassLoader
- 基础的类加载器, PathClassLoader和DexClassLoader只是在其构造函数上的简单封装
-
BootClassLoader: 父类的构造器
学习文章链接