前言

ClassLoader类加载,是动态加载机制及现在火热的插件化机制中很基础但同时又很重要的知识点,通过学习这一章节,能在脑海中浮现出Android系统、应用的加载原理整个框架。

什么是ClassLoader

翻译过来就是类加载器,见名知义。类在生成字节码.class文件后,从物理文件地址加载到Dalvik/ART虚拟机中使用。

Android中的类体现

这里会提到Android中使用较多的两种ClassLoader,当然还有属于JVM的ClassLoader,有兴趣的可以自行查阅。

BootClassLoader: Dalvik/ART虚拟机用于加载Android系统类的Loader,应用层通过获取父ClassLoader的最终项。

PathClassLoader: 我们知道,打包APK后实际上是把java文件都生成dex文件,而这个Loader就是在应用启动时,加载已安装APK的dex文件。

DexClassLoader: 常见的动态加载机制都用这个类,传入指定路径加载指定dex文件。

加载原理

ClassLoader使用的是双亲委托机制。双亲委派模型,旨在于让顶级父类加载器先加载类,若不成功,则一层层往下加载,最终到当前加载器。这样做的目的是保持类加载系统的稳定性,不会出现不同加载器加载同一个类时,出现多个类实例。

4c1b0924a14f

Android-ClassLoader加载机制.png

类加载源码解析

关注ClassLoader的loadClass方法

BootClassLoader用于加载系统层的类,比较特殊,loadClass查询虚拟机中没有缓存时最终会调用到findClass ——> Native 方法 Class.classForName:

4c1b0924a14f

BootClassLoader-findClass.png

其余的loadClass都是调用到下图中,先查找当前loader是否有加载到该类的缓存,若没有,就获取父加载器查找,递归直到父加载器不存在。

因此总体来看这是一个先向上委托父加载器,再向下查找的过程。若查询为null,将从当前加载器加载类,最终会调用loadClass方法,加载成功就跳出递归。

4c1b0924a14f

ClassLoader-loadClass.png

DexClassLoader和PathClassLoader 都是继承于BaseDexClassLoader,从构造方法可知,optimizedDirectory这个参数已经无效了,因为已经没有向下传递的,我们一起直接看看它的findClass方法。

4c1b0924a14f

BaseDexClassLoader.png

4c1b0924a14f

BaseDexClassLoader-findclass.png

实际上调用的是DexPathList.findClass方法,这里有个dexElements,看看他是如何构造出来的。

4c1b0924a14f

DexPathList-findClass.png

根据我们创建ClassLoader时,传入的dexPath,支持APK、DEX、JAR文件全路径,以/分隔符分开。分别调用loadDexFile,这就是我们生成最终可用的dex文件的过程。最终构造出dexElements。

4c1b0924a14f

DexPathList-makePathElements.png

4c1b0924a14f

DexFile-loadDexFile.png

最终调用loadClassBinaryName,这个方法最终会调到底层Native 方法DextFile.defineClassNative。

4c1b0924a14f

DexFile-loadClassBinaryName.png

4c1b0924a14f

DexFile-defineClassNative.png

根据以上的加载原理,已经能大致了解Android类加载的过程。就是我们使用DexClassLoader或PathClassLoader加载类时,系统究竟做了什么,也是能有个印象,为我们接下来更加深入到类加载器在虚拟机中是如何运行的知识点建立一个基础。

使用方式

DexClassLoader 构造方法参数说明

4c1b0924a14f

DexClassLoader.png

参数名

描述

dexPath

待提取dex的文件全路径,多个时以 ":" 分隔符(在Android中以File.pathSeparator定义)隔开

optimizedDirectory

提取到dex文件后存放文件夹路径,但现在已经无效了

libraryPath

so文件所在文件夹路径, 多个时以 ":" 分隔符(在Android中以File.pathSeparator定义)隔开

parent

父classloader

举个例子吧

本地项目新建一个系统已实现类 —— TextUtils.java,其中的isEmpty的方法体实现变更,项目中能否调用到我更改后的方法逻辑呢?

4c1b0924a14f

TextUtils.png

4c1b0924a14f

checkTextUtils.png

答案显而易见是不可以的,前面我们学到,类加载委托是从下至上,类查找是从上至下的,顶级类加载器父类BootClassloader查找到TextUtils时,会首先把类加载入缓存。级别更低的ClassLoader均从缓存获取成功,因此项目中使用PathClassloader才能加载到的TextUtils是无法成功的。

4c1b0924a14f

checkTextUtils-result.png

用DexClassLoader的方式加载外部dex文件,首先新建类Dextest用于打包并使用dx工具生成dextest.jar放在外部扩展文件夹dx/下。接着构建DexClassLoader并反射触发即可成功。

4c1b0924a14f

Dextest.png

4c1b0924a14f

loadExternDex.png

4c1b0924a14f

loadExternDex-result.png

总结

我们若想了解各种加载流程,还是需要多深入源码,Android-ClassLoader实现逻辑算是非常清晰易懂,但对我们日常开发如插件化方案会有非常大的帮助。

阅读源码由于各模块代码体量都是不小的,为了效率应该挑重点看,也可以结合博客加快自己看源码的进程。

native底层代码阅读:

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐