Android热补丁动态修复技术(一):从Dex分包原理到热补丁

2024-05-13 06:48

本文主要是介绍Android热补丁动态修复技术(一):从Dex分包原理到热补丁,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

一、参考

博文:安卓App热补丁动态修复技术介绍——by QQ空间终端开发团队
博文:Android dex分包方案——by 猫的午后
开源项目:https://github.com/jasonross/Nuwa
开源项目:https://github.com/dodola/HotFix
感谢以上几位大神分享的技术知识!

关于热补丁技术,以上文章已经做了很详细的描述。但是细节上的东西都一带而过,这里会做出更为详细的说明,更适合初学者学习这门技术。

二、Dex分包方案的由来

###2.1 Dalvik限制
众所周知,当apk解压后里面是只有一个classes.dex文件的,而这个dex文件里面就包含了我们项目的所有.class文件。

但是当一个app功能越来越复杂,可能会出现两个问题:

  1. 编译失败,因为一个dvm中存储方法id用的是short类型,导致dex中方法不能超过65536个
  2. 你的apk在android 2.3之前的机器无法安装,因为dex文件过大(用来执行dexopt的内存只分配了5M)

2.2 解决方案

针对上述两个问题,有人研究出了dex分包方案。
原理就是将编译好的class文件拆分打包成两个dex,绕过dex方法数量的限制以及安装时的检查,在运行时再动态加载第二个dex文件中。

除了第一个dex文件(即正常apk包唯一包含的Dex文件),其它dex文件都以资源的方式放在安装包中,并在Application的onCreate回调中被注入到系统的ClassLoader。因此,对于那些在注入之前已经引用到的类(以及它们所在的jar),必须放入第一个Dex文件中。

##三、Dex分包的原理——ClassLoader
接下来我们就来看看,如何将第二个dex文件注入到系统中。

3.1 ClassLoader体系

我们都知道,java执行程序的时候是需要先将字节码加载到jvm之后才会被执行的,而这个过程就是使用到了ClassLoader类加载器。Android也是如此

以下是DVM的ClassLoader体系

这里写图片描述

查看官方文档可以知道以下两点:
1.Android系统是通过PathClassLoader加载系统类和已安装的应用的。
Android uses this class for its system class loader and for its application class loader(s).

2.而DexClassPath则可以从一个jar包或者未安装的apk中加载dex
A class loader that loads classes from .jar and .apk files containing a classes.dex entry. This can be used to execute code not installed as part of an application.

从这里就可以看出,动态加载dex的时候我们应该使用DexClassLoader

3.2 ClassLoader源码分析

源码可以到这个网站查阅:http://androidxref.com/

DexClassLoader和PathClassLoader都只重写了BaseDexClassLoader的构造而已,而具体的加载逻辑则在BaseDexClassLoader中。

这部分源码都很简单,请务必看懂

BaseDexClassLoader部分源码

public class BaseDexClassLoader extends ClassLoader {private final DexPathList pathList;/*** Constructs an instance.** @param dexPath the list of jar/apk files containing classes and* resources, delimited by {@code File.pathSeparator}, which* defaults to {@code ":"} on Android* @param optimizedDirectory directory where optimized dex files* should be written; may be {@code null}* @param libraryPath the list of directories containing native* libraries, delimited by {@code File.pathSeparator}; may be* {@code null}* @param parent the parent class loader*/public BaseDexClassLoader(String dexPath, File optimizedDirectory,String libraryPath, ClassLoader parent) {super(parent);this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory);}@Overrideprotected Class<?> findClass(String name) throws ClassNotFoundException {List<Throwable> suppressedExceptions = new ArrayList<Throwable>();Class c = pathList.findClass(name, suppressedExceptions);if (c == null) {ClassNotFoundException cnfe = new ClassNotFoundException("Didn't find class \"" + name + "\" on path: " + pathList);for (Throwable t : suppressedExceptions) {cnfe.addSuppressed(t);}throw cnfe;}return c;}
}

从源码得知,当我们需要加载一个class时,实际是从pathList中去找的,而pathList则是DexPathList的一个实体。

DexPathList部分源码:

/*package*/ final class DexPathList {private static final String DEX_SUFFIX = ".dex";private static final String JAR_SUFFIX = ".jar";private static final String ZIP_SUFFIX = ".zip";private static final String APK_SUFFIX = ".apk";/** class definition context */private final ClassLoader definingContext;/*** List of dex/resource (class path) elements.* Should be called pathElements, but the Facebook app uses reflection* to modify 'dexElements' (http://b/7726934).*/private final Element[] dexElements;/*** Finds the named class in one of the dex files pointed at by* this instance. This will find the one in the earliest listed* path element. If the class is found but has not yet been* defined, then this method will define it in the defining* context that this instance was constructed with.** @param name of class to find* @param suppressed exceptions encountered whilst finding the class* @return the named class or {@code null} if the class is not* found in any of the dex files*/public Class findClass(String name, List<Throwable> 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;}
}

从这段源码可以看出,dexElements是用来保存dex的数组,而每个dex文件其实就是DexFile对象。遍历dexElements,然后通过DexFile去加载class文件,加载成功就返回,否则返回null

通常情况下,dexElements数组中只会有一个元素,就是apk安装包中的classes.dex
而我们则可以通过反射,强行的将一个外部的dex文件添加到此dexElements中,这就是dex的分包原理了。
这也是热补丁修复技术的原理。

##四、热补丁修复技术的原理
上面的源码,我们注意到一点,如果两个dex中存在相同的class文件会怎样?
先从第一个dex中找,找到了直接返回,遍历结束。而第二个dex中的class永远不会被加载进来。
简而言之,两个dex中存在相同class的情况下,dex1的class会覆盖dex2的class。
盗一下QQ空间的图,如图:classes1.dex中的Qzone.class并不会被加载
这里写图片描述

而热补丁技术则利用了这一特性,当一个app出现bug的时候,我们就可以将出现那个bug的类修复后,重新编译打包成dex,插入到dexElements的前面,那么出现bug的类就会被覆盖,app正常运行,这就是热修复的原理了。
这里写图片描述

五、本章结束

这章为大家介绍了热补丁技术的原理,但是大家可能并不会实际操作。

  1. 怎么通过反射将dex插入到elements
  2. 怎么讲修复后的类打包成dex
    这将是下一篇博客的内容,感谢阅读。

这篇关于Android热补丁动态修复技术(一):从Dex分包原理到热补丁的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/984975

相关文章

Java使用Javassist动态生成HelloWorld类

《Java使用Javassist动态生成HelloWorld类》Javassist是一个非常强大的字节码操作和定义库,它允许开发者在运行时创建新的类或者修改现有的类,本文将简单介绍如何使用Javass... 目录1. Javassist简介2. 环境准备3. 动态生成HelloWorld类3.1 创建CtC

ShardingProxy读写分离之原理、配置与实践过程

《ShardingProxy读写分离之原理、配置与实践过程》ShardingProxy是ApacheShardingSphere的数据库中间件,通过三层架构实现读写分离,解决高并发场景下数据库性能瓶... 目录一、ShardingProxy技术定位与读写分离核心价值1.1 技术定位1.2 读写分离核心价值二

深度解析Python中递归下降解析器的原理与实现

《深度解析Python中递归下降解析器的原理与实现》在编译器设计、配置文件处理和数据转换领域,递归下降解析器是最常用且最直观的解析技术,本文将详细介绍递归下降解析器的原理与实现,感兴趣的小伙伴可以跟随... 目录引言:解析器的核心价值一、递归下降解析器基础1.1 核心概念解析1.2 基本架构二、简单算术表达

深入浅出Spring中的@Autowired自动注入的工作原理及实践应用

《深入浅出Spring中的@Autowired自动注入的工作原理及实践应用》在Spring框架的学习旅程中,@Autowired无疑是一个高频出现却又让初学者头疼的注解,它看似简单,却蕴含着Sprin... 目录深入浅出Spring中的@Autowired:自动注入的奥秘什么是依赖注入?@Autowired

修复已被利用的高危漏洞! macOS Sequoia 15.6.1发布

《修复已被利用的高危漏洞!macOSSequoia15.6.1发布》苹果公司于今日发布了macOSSequoia15.6.1更新,这是去年9月推出的macOSSequoia操作... MACOS Sequoia 15.6.1 正式发布!此次更新修复了一个已被黑客利用的严重安全漏洞,并解决了部分中文用户反馈的

从原理到实战解析Java Stream 的并行流性能优化

《从原理到实战解析JavaStream的并行流性能优化》本文给大家介绍JavaStream的并行流性能优化:从原理到实战的全攻略,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的... 目录一、并行流的核心原理与适用场景二、性能优化的核心策略1. 合理设置并行度:打破默认阈值2. 避免装箱

Android协程高级用法大全

《Android协程高级用法大全》这篇文章给大家介绍Android协程高级用法大全,本文结合实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友跟随小编一起学习吧... 目录1️⃣ 协程作用域(CoroutineScope)与生命周期绑定Activity/Fragment 中手

Python中的filter() 函数的工作原理及应用技巧

《Python中的filter()函数的工作原理及应用技巧》Python的filter()函数用于筛选序列元素,返回迭代器,适合函数式编程,相比列表推导式,内存更优,尤其适用于大数据集,结合lamb... 目录前言一、基本概念基本语法二、使用方式1. 使用 lambda 函数2. 使用普通函数3. 使用 N

MyBatis-Plus 与 Spring Boot 集成原理实战示例

《MyBatis-Plus与SpringBoot集成原理实战示例》MyBatis-Plus通过自动配置与核心组件集成SpringBoot实现零配置,提供分页、逻辑删除等插件化功能,增强MyBa... 目录 一、MyBATis-Plus 简介 二、集成方式(Spring Boot)1. 引入依赖 三、核心机制

Python中高级文本模式匹配与查找技术指南

《Python中高级文本模式匹配与查找技术指南》文本处理是编程世界的永恒主题,而模式匹配则是文本处理的基石,本文将深度剖析PythonCookbook中的核心匹配技术,并结合实际工程案例展示其应用,希... 目录引言一、基础工具:字符串方法与序列匹配二、正则表达式:模式匹配的瑞士军刀2.1 re模块核心AP