Android热更新-类的加载机制源码分析

这次简单的了解一下Android中的类加载机制,如下Android中的类加载类:

 

在说Android的类加载机制之前先说一下Java的类加载机制,两者都是的大同小异,只是Android中的加载的是dex文件,Java中加载的是.class文件

 

Java 中的类何时被加载器加载

在 Java 程序启动的时候,并不会一次性加载程序中所有的 .class 文件,而是在程序的运行过程中,动态地加载相应的类到内存中。

 

通常情况下,Java 程序中的 .class 文件会在以下 2 种情况下被 ClassLoader 主动加载到内存中:

  1. 调用类构造器
  2. 调用类中的静态(static)变量或者静态方法

 

Java 中 ClassLoader

 

JVM 中自带 3 个类加载器:

  1. 启动类加载器 BootstrapClassLoader
  2. 扩展类加载器 ExtClassLoader (JDK 1.9 之后,改名为 PlatformClassLoader)
  3. 系统加载器 APPClassLoader

以上 3 者在 JVM 中有各自分工,但是又互相有依赖。

APPClassLoader 系统类加载器

部分源码如下:

 

 

可以看出,AppClassLoader 主要加载系统属性“java.class.path”配置下类文件,也就是环境变量 CLASS_PATH 配置的路径。因此 AppClassLoader 是面向用户的类加载器,我们自己编写的代码以及使用的第三方 jar 包通常都是由它来加载的。

 

ExtClassLoader 扩展类加载器

部分源码如下:

 

 

可以看出,ExtClassLoader 加载系统属性“java.ext.dirs”配置下类文件

 

BootstrapClassLoader 启动类加载器

BootstrapClassLoader 同上面的两种 ClassLoader 不太一样。

首先,它并不是使用 Java 代码实现的,而是由 C/C++ 语言编写的,它本身属于虚拟机的一部分。因此我们无法在 Java 代码中直接获取它的引用。如果尝试在 Java 层获取 BootstrapClassLoader 的引用,系统会返回 null。

 

 

双亲委派模式(Parents Delegation Model)

所谓双亲委派模式就是,当类加载器收到加载类或资源的请求时,通常都是先委托给父类加载器加载,也就是说,只有当父类加载器找不到指定类或资源时,自身才会执行实际的类加载过程。这个Android里面也是同理,使用的双亲委派模型,这是Android中的源码;

 

以下事Java中的源码,和上面的源码是一样的;

解释说明:

  1. 判断该 Class 是否已加载,如果已加载,则直接将该 Class 返回。
  2. 如果该 Class 没有被加载过,则判断 parent 是否为空,如果不为空则将加载的任务委托给parent。
  3. 如果 parent == null,则直接调用 BootstrapClassLoader 加载该类。
  4. 如果 parent 或者 BootstrapClassLoader 都没有加载成功,则调用当前 ClassLoader 的 findClass 方法继续尝试加载。

那这个 parent 是什么呢? 我们可以看下 ClassLoader 的构造器,如下:

 

 

可以看出,在每一个 ClassLoader 中都有一个 CLassLoader 类型的 parent 引用,并且在构造器中传入值。如果我们继续查看源码,可以看到 AppClassLoader 传入的 parent 就是 ExtClassLoader,而 ExtClassLoader 并没有传入任何 parent,也就是 null。

 

自定义 ClassLoader 步骤

  1. 自定义一个类继承抽象类 ClassLoader。
  2. 重写 findClass 方法。
  3. 在 findClass 中,调用 defineClass 方法将字节码转换成 Class 对象,并返回。

 

 

 

Android中的ClassLoader

本质上,Android 和传统的 JVM 是一样的,也需要通过 ClassLoader 将目标类加载到内存,类加载器之间也符合双亲委派模型。但是在 Android 中, ClassLoader 的加载细节有略微的差别。

在 Android 虚拟机里是无法直接运行 .class 文件的,Android 会将所有的 .class 文件转换成一个 .dex 文件,并且 Android 将加载 .dex 文件的实现封装在 BaseDexClassLoader 中,而我们一般只使用它的两个子类:PathClassLoader 和 DexClassLoader。

 

PathClassLoader

PathClassLoader 用来加载系统 apk 和被安装到手机中的 apk 内的 dex 文件。它的 2 个构造函数如下:

 

 

参数说明:

  1. dexPath:dex 文件路径,或者包含 dex 文件的 jar 包路径;
  2. librarySearchPath:C/C++ native 库的路径。

PathClassLoader 里面除了这 2 个构造方法以外就没有其他的代码了,具体的实现都是在 BaseDexClassLoader 里面,其 dexPath 比较受限制,一般是已经安装应用的 apk 文件路径。

当一个 App 被安装到手机后,apk 里面的 class.dex 中的 class 均是通过 PathClassLoader 来加载的;这个也印证了我们文章开头的上面的那幅图;Android应用中的类,均是通过PathClasLoader来加载的;

 

DexClassLoader

先来看官方对 DexClassLoader 的描述:

 

A class loader that loads classes from .jar and .apk filescontaining a classes.dex entry. 

This can be used to execute code notinstalled as part of an application.

 

很明显,对比 PathClassLoader 只能加载已经安装应用的 dex 或 apk 文件,DexClassLoader 则没有此限制,可以从 SD 卡上加载包含 class.dex 的 .jar 和 .apk 文件,这也是插件化和热修复的基础,在不需要安装应用的情况下,完成需要使用的 dex 的加载。

 

 

参数说明:

  1. dexPath:包含 class.dex 的 apk、jar 文件路径 ,多个路径用文件分隔符(默认是“:”)分隔。
  2. optimizedDirectory:用来缓存优化的 dex 文件的路径,即从 apk 或 jar 文件中提取出来的 dex 文件。该路径不可以为空,且应该是应用私有的,有读写权限的路径。

 

一般使用 DexClassLoader 加载 HotFix patch包的大概步骤(这里使用的事jar包,和我们平时使用的dex包是一样的);

 

 

总结

  1. ClassLoader 就是用来加载 class 文件的,不管是 jar 中还是 dex 中的 class。
  2. Java 中的 ClassLoader 通过双亲委托来加载各自指定路径下的 class 文件。
  3. 可以自定义 ClassLoader,一般覆盖 findClass() 方法,不建议重写 loadClass 方法。
  4. Android 中常用的两种 ClassLoader 分别为:PathClassLoader 和 DexClassLoader。

 

PathClassLoader来实现热修复;

首先事原理探究,如果使用PathClassLoader来实现热修复,要先看看Android中的类加载机制,Android应用的类加载都是使用的PathClassLoader,PathClassLoader的父类又是BaseDexClassLoader,基本的具体类加载的实现都是BaseDexClassLoader;

我们都知道实现一个ClassLoader就去看它的findClass方法,先去看看BaseDexClassLoader#findClass方法,如下所示;

 

从上面看到这里的核心实在pathList.findClass这里面实现的,我们看看pathList,

它是DexPathList类型的一个变量,我们在去看看DexPathList中的findClass方法;

 

可以看到使用的是遍历dexElements这个数组,然后加载类,返回class;可以看到上面的遍历,一旦发现了这个类,那么就直接return返回结束遍历,我们可以将我们自己需要更新的类的dex文件生成Element,然后将这个element插入到elements这个数组中的最前面,然乎下次需要加载这个类的时候,找到了这个类,就不会在继续遍历我们apk文件中的类,达到了所谓的热修复的效果;所以我们可以使用如下的思路来实现热更新;

 

  1. 首先拿到PathClassLoader;
  2. 然后通过反射拿到PathClassLoader中的变量pathlist这个Field字段;
  3. 然后通过反射拿到pathlist这个实例对应的类DexPathList中的的变量dexElements数组这个字段变量值;
  4. 最后一个难点就是如何将dex文件变成element类,我们可以参照源码中的实现,如下

使用了makeDexElements这个方法,我们可以使用反射拿到这个方法,然后使用反射调用makeDexElements这个方法,将我们自己的patch.dex文件转换为element数组

  1. 在拿到我们自己的elements数组后,在将我们自己的elements数组于与DexPathList.dexElements中的数组合并,将这个合并的数组使用反射将值赋给dexElements这个field
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值