Android动态加载系列 —— ClassLoader加载原理

前言

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

什么是ClassLoader

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

Android中的类体现

  • 这里会提到Android中使用较多的两种ClassLoader,当然还有属于JVM的ClassLoader,有兴趣的可以自行查阅。
  1. BootClassLoader: Dalvik/ART虚拟机用于加载Android系统类的Loader,应用层通过获取父ClassLoader的最终项。
  2. PathClassLoader: 我们知道,打包APK后实际上是把java文件都生成dex文件,而这个Loader就是在应用启动时,加载已安装APK的dex文件。
  3. DexClassLoader: 常见的动态加载机制都用这个类,传入指定路径加载指定dex文件。

加载原理

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


    Android-ClassLoader加载机制.png

类加载源码解析

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


    BootClassLoader-findClass.png
  2. 其余的loadClass都是调用到下图中,先查找当前loader是否有加载到该类的缓存,若没有,就获取父加载器查找,递归直到父加载器不存在。
    因此总体来看这是一个先向上委托父加载器,再向下查找的过程。若查询为null,将从当前加载器加载类,最终会调用loadClass方法,加载成功就跳出递归。

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


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


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


    DexPathList-makePathElements.png
DexFile-loadDexFile.png
  1. 最终调用loadClassBinaryName,这个方法最终会调到底层Native 方法DextFile.defineClassNative。


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

使用方式

  • DexClassLoader 构造方法参数说明


    DexClassLoader.png
参数名 描述
dexPath 待提取dex的文件全路径,多个时以 ":" 分隔符(在Android中以File.pathSeparator定义)隔开
optimizedDirectory 提取到dex文件后存放文件夹路径,但现在已经无效了
libraryPath so文件所在文件夹路径, 多个时以 ":" 分隔符(在Android中以File.pathSeparator定义)隔开
parent 父classloader

举个例子吧

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


    TextUtils.png
checkTextUtils.png

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


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

总结

  • 我们若想了解各种加载流程,还是需要多深入源码,Android-ClassLoader实现逻辑算是非常清晰易懂,但对我们日常开发如插件化方案会有非常大的帮助。
  • 阅读源码由于各模块代码体量都是不小的,为了效率应该挑重点看,也可以结合博客加快自己看源码的进程。

native底层代码阅读:

  • ART 加载类

你可能感兴趣的:(Android动态加载系列 —— ClassLoader加载原理)