博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
热修复之类加载方案 笔记整理
阅读量:3566 次
发布时间:2019-05-20

本文共 4449 字,大约阅读时间需要 14 分钟。

热修复分为:代码修复、资源修复、动态链接修复

其中,代码修复又分为:类加载方案、底层替换方案、Instant Run 方案。

本篇关于代码修复的类加载方案的笔记整理。

涉及源码版本为 Android 7.1.1。

参考文章:

1、
2、《Android 进阶解密》


1、理论基础

类加载方案是基于 Dex 分包方案的。

Dex 分包方案主要做的是在打包的时候将应用代码分成多个 Dex,将应用启动时必须用到的类和这些类的直接引用类放到主 Dex 中,其他代码放到次 Dex 中。当应用启动时先加载主 Dex,等到应用启动后再动态加载次 Dex。

而关于类的加载,就是遍历所有的 Dex 文件,从中去加载目标类。这里就涉及到了一个类 DexPathList

更进一步的,是具体的是,则是涉及到类加载器。

对于类的加载,是通过 ClassLoader 来进行的,基于双亲委托模式,会先通过具体的加载器的父加载器来加载类,如果父加载器没加载到,则会调用自身的 findClass() 方法来自行加载。

涉及到的加载器包括 DexClassLoaderPathClassLoader

  1. DexClassLoader 可以加载 dex 文件以及包含 dex 的压缩文件(apk 和 jar 文件),而且可以加载指定路径中的 dex 文件,包括外部存储空间的。
  2. PathClassLoader 则是 Android 系统用来加载系统类和应用程序的类。通常用来加载已经安装的 apk 的 dex 文件(安装的 apk 的 dex 文件会存在 /data/dalvik-cache)。

上述两个 ClassLoader 都继承自 BaseDexClassLoader

public BaseDexClassLoader(String dexPath, File optimizedDirectory,                          String librarySearchPath, ClassLoader parent) {
super(parent); // 实例化成员变量 pathList this.pathList = new DexPathList(this, dexPath, librarySearchPath, optimizedDirectory);}@Overrideprotected Class
findClass(String name) throws ClassNotFoundException {
List
suppressedExceptions = new ArrayList
(); Class c = pathList.findClass(name, suppressedExceptions); if (c == null) {
... // 如果没有加载到目标 Class 则会抛出异常 } return c;}

可以看到,通过 BaseDexClassLoader#findClass() 会进一步调用前面说到的 DexPathList 类型的 pathList.findClass()

public Class findClass(String name, List
suppressed) {
for (Element element : dexElements) {
DexFile dex = element.dexFile; if (dex != null) {
Class clazz = dex.loadClassBinaryName(name, definingContext, suppressed); if (clazz != null) {
return clazz; } } } if (dexElementsSuppressedExceptions != null) {
suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions)); } return null;}

DexPathList#findClass() 中,会遍历 dexElements 数组,该数组的元素的为 DexPathList.Element 类型,而其内部又封装了 DexFile 成员变量,该变量就对应着实际的 dex 文件,更进一步的说,加载 Class 实际上是通过 DexFile 来实现的。

因此,类加载方案的实现,就是将补丁 dex 对应的 Element 插入到应用对应的加载器的 pathListdexElements 数组的靠前位置,从而使得后面同名的 Class 不被加载。

2、具体实现

参考 Demo:

补充两点:

(1)是关于 CLASS_ISPREVERIFIED 的问题(涉及到 Dalvik 虚拟机),具体参见:,因此在测试的时候要使用基于 ART 虚拟机的机型,即 Android 5.0 及以上版本。

(2)由于 Android 9.0 隐藏了部分 API,所以无法实现反射替换对应的 dexElements 数组,因此在测试的要使用 9.0 以下的手机。

关键部分代码:

public void doHotFix(Context context) throws IllegalAccessException, NoSuchFieldException, ClassNotFoundException {
if (context == null) {
return; } // 补丁存放目录为 /storage/emulated/0/Android/data/com.lxbnjupt.hotfixdemo/files/patch // 注意,这里的 dexFile 是一个目录 File dexFile = context.getExternalFilesDir(DEX_DIR); if (dexFile == null || !dexFile.exists()) {
Log.e(TAG,"热更新补丁目录不存在"); return; } // 得到 new DexClassLoader 时需要的存储路径 File odexFile = context.getDir(OPTIMIZE_DEX_DIR, Context.MODE_PRIVATE); if (!odexFile.exists()) {
odexFile.mkdir(); } // 获取 /storage/emulated/0/Android/data/com.lxbnjupt.hotfixdemo/files/patch // 目录下的所有文件,用于找出里面的补丁 dex File[] listFiles = dexFile.listFiles(); if (listFiles == null || listFiles.length == 0) {
return; } // 获取补丁 dex 文件路径集合 String dexPath = getPatchDexPath(listFiles); String odexPath = odexFile.getAbsolutePath(); // 获取应用对应的 PathClassLoader PathClassLoader pathClassLoader = (PathClassLoader) context.getClassLoader(); // 构建 DexClassLoader,用于加载补丁 dex // DexClassLoader 构造方法的四个参数: // 第一个:dex 文件相关路径集合,多个路径用文件分隔符分隔,默认文件分隔符为 : // 第二个:解压的 dex 文件的存储路径,必须是一个内部存储路径 // 第三个:包含 C/C++ 库的路径集合,可以为 null // 第四个:父加载器 DexClassLoader dexClassLoader = new DexClassLoader(dexPath, odexPath, null, pathClassLoader); // 这里要新 new 一个 DexClassLoader 的原因就是为了借助系统来构建出补丁 dex 对应 // 的 Element 元素的数组,从而插入到应用的 PathClassLoader 的 // pathList.dexElements 中 // 通过反射获取 PathClassLoader 的 Element 数组 Object pathElements = getDexElements(pathClassLoader); // 获取构建的 DexClassLoader 的 Element 数组 Object dexElements = getDexElements(dexClassLoader); // 合并 Element 数组 Object combineElementArray = combineElementArray(pathElements, dexElements); // 通过反射,将合并后的 Element 数组赋值给 PathClassLoader 中 pathList 里面的 // dexElements 变量 setDexElements(pathClassLoader, combineElementArray);}

注意,由于类加载之后是无法被主动卸载的,因此类加载方法需要重启 App 后让 ClassLoader 重新加载新的类。

而且重启应用之后,如果没有触发加载补丁类,则应用还是会加载原来的 BUG 类。

转载地址:http://crerj.baihongyu.com/

你可能感兴趣的文章
前端网页学习7(css背景属性)
查看>>
前端网页学习8(css三大特性:层叠性,继承性,优先级)
查看>>
前端网页学习9(css盒子)
查看>>
python学习8(列表)
查看>>
JavaScript学习(new1)
查看>>
http GET 和 POST 请求的优缺点、区别以及误区
查看>>
JVM的4种垃圾回收算法、垃圾回收机制
查看>>
什么是分布式事务
查看>>
常用的分布式事务解决方案
查看>>
设计模式:单例模式 (关于饿汉式和懒汉式)
查看>>
一致性Hash算法
查看>>
更新Navicat Premium 后打开数据库出现1146 - Table 'performance_schema.session_variables' doesn't exist
查看>>
安装rabbitmq时踩的坑
查看>>
2021-06-09数据库添加多条数据
查看>>
简单的JAVA小作品
查看>>
CMake下载
查看>>
未调用fflush产生的图片文件无法打开问题
查看>>
SQL 约束(二)
查看>>
SQL ALTER用法(三)
查看>>
SQL where子句及查询条件语句(六)
查看>>