一篇文章搞懂热修复类加载方案原理( 二 )

  • URLClassLoader 类和 JDK8 中的 URLClassLoader 类的代码是一样的,它继承自 SecureClassLoader,用来通过URl路径从 jar 文件和文件夹中加载类和资源 。
  • BaseDexClassLoader 继承自 ClassLoader,是抽象类 ClassLoader 的具体实现类,PathClassLoader 和 DexClassLoader 都继承它 。
  • 下面看看运行一个 Android 程序需要用到几种类型的类加载器
    class MainActivity : AppCompatActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_main)var classLoader = this.classLoader// 打印 ClassLoader 继承关系while (classLoader != null) {Log.d("MainActivity", classLoader.toString())classLoader = classLoader.parent}}}将 MainActivity 的类加载器打印出来,并且打印当前类加载器的父加载器,直到没有父加载器,则终止循环 。打印结果如下:
    com.zhgqthomas.github.hotfixdemo D/MainActivity: dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/com.zhgqthomas.github.hotfixdemo-2/base.apk"],nativeLibraryDirectories=[/data/app/com.zhgqthomas.github.hotfixdemo-2/lib/arm64, /oem/lib64, /system/lib64, /vendor/lib64]]]com.zhgqthomas.github.hotfixdemo D/MainActivity: java.lang.BootClassLoader@4d7e926可以看到有两种类加载器,一种是 PathClassLoader,另一种则是 BootClassLoader。DexPathList 中包含了很多路径,其中 /data/app/com.zhgqthomas.github.hotfixdemo-2/base.apk 就是示例应用安装在手机上的位置 。
    双亲委托模式类加载器查找 Class 所采用的是双亲委托模式,所谓双亲委托模式就是首先判断该 Class 是否已经加载,如果没有则不是自身去查找而是委托给父加载器进行查找,这样依次的进行递归,直到委托到最顶层的 BootstrapClassLoader,如果 BootstrapClassLoader 找到了该 Class,就会直接返回,如果没找到,则继续依次向下查找,如果还没找到则最后会交由自身去查找 。这是 JDK 中 ClassLoader 的实现逻辑,Android 中的 ClassLoader 在 findBootstrapClassOrNull 方法的逻辑处理上存在差异 。
    // ClassLoader.javaprotected Class<?> loadClass(String name, boolean resolve)throws ClassNotFoundException{// First, check if the class has already been loadedClass c = findLoadedClass(name);if (c == null) {long t0 = System.nanoTime();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.long t1 = System.nanoTime();c = findClass(name);// this is the defining class loader; record the stats}}return c;}上面的代码很容易理解,首先会查找加载类是否已经被加载了,如果是直接返回,否则委托给父加载器进行查找,直到没有父加载器则会调用 findBootstrapClassOrNull 方法 。
    下面看一下 findBootstrapClassOrNull 在 JDK 和 Android 中分别是如何实现的
    // JDK ClassLoader.javaprivate Class<?> findBootstrapClassOrNull(String name){if (!checkName(name)) return null;return findBootstrapClass(name);}JDK 中 findBootstrapClassOrNull 会最终交由 BootstrapClassLoader 去查找 Class 文件,上面提到过 BootstrapClassLoader 是由 C++ 实现的,所以 findBootstrapClass 是一个 native 的方法
    // JDK ClassLoader.javaprivate native Class<?> findBootstrapClass(String name);在 Android 中 findBootstrapClassOrNull 的实现跟 JDK 是有差别的
    // Androidprivate Class<?> findBootstrapClassOrNull(String name){return null;}Android 中因为不需要使用到 BootstrapClassLoader 所以该方法直接返回来 null
    正是利用类加载器查找 Class 采用的双亲委托模式,所以可以利用反射修改类加载器加载 dex 相关文件的顺序,从而达到热修复的目的
    类加载过程通过上面分析可知
    • PathClassLoader 可以加载 Android 系统中的 dex 文件
    • DexClassLoader 可以加载任意目录的 dex/zip/apk/jar 文件,但是要指定 optimizedDirectory。
    通过代码可知这两个类只是继承了 BaseDexClassLoader,具体的实现依旧是由 BaseDexClassLoader 来完成 。
    BaseDexClassLoader// libcore/dalvik/src/main/java/dalvik/system/BaseDexClassLoader.javapublic class BaseDexClassLoader extends ClassLoader {...private final DexPathList pathList;public BaseDexClassLoader(String dexPath, File optimizedDirectory,String librarySearchPath, ClassLoader parent) {this(dexPath, optimizedDirectory, librarySearchPath, parent, false);}/*** @hide*/public BaseDexClassLoader(String dexPath, File optimizedDirectory,String librarySearchPath, ClassLoader parent, boolean isTrusted) {super(parent);this.pathList = new DexPathList(this, dexPath, librarySearchPath, null, isTrusted);if (reporter != null) {reportClassLoaderChain();}}...public BaseDexClassLoader(ByteBuffer[] dexFiles, ClassLoader parent) {// TODO We should support giving this a library search path maybe.super(parent);this.pathList = new DexPathList(this, dexFiles);}...}


    推荐阅读