Android热修复一:热修复的流程

下一篇:Android热修复二(手写热修复代码)

在听了lance老师的热修复理论之后,决定写一篇文章,把我理解的全部记下来

之前也多少了解过热修复,当下的热修复方案应该按技术分为三种:

  1. 底层替换方法
  2. instant run 方法
  3. 基于类加载机制

至于前两种就简单说下,这次主要分析一下第三种。

1.底层替换方法:典型框架(阿里 AndFix)

andfix通过在native层hook java层的代码来实现方法替换,可以做到及时生效。感觉很厉害,而且这种方法颗粒度比替换class要小。但是阿里在几年前已经停止维护了,可能是由于ndk的方案系统兼容性不好吧。

2.Instant Run方法 : 典型框架:美团Robus)

这个框架的方式是在编译期间在每个方法内都插入类似下面一段代码:

注意是每个方法都要插入,最终生成的class肯定会代码增加不少吧。如果需要修改原来方法的逻辑,好像只需要修改changeQuickRedict的值就可以了(这个是我猜测的,等下看源码验证)。它也可以做到即时生效。

3.类加载机制:典型(Tinker热修复和QQZone热修复)

在了解这一类型的热修复之前,必须要了解Android中的类加载机制。

Android的类加载:

上面是盗取了lance老师的图,很清晰了展示了android classLoader的继承结构。首先要注意的几个重要的类:

  1. BootClassLoader: ClassLoader的内部类兼子类,用来加载Android Framework的class文件。 比如Activity,Service,BroadcastReceiver等。
  2. PathClassLoader: 用来加载自己写的程序中的类。 比如自定义的MainActivity,MyService等。
  3. DexClassLoader: 功能上与PathClassLoader基本相同,只是在构造方法参数上不同, PathClassLoadaer使用默认opt目录,而DexClassLoader在8.0之前可以指定opt目录,但是8.0之后这个参数失效了。
  4. BaseDexClassLoader:提供了findClass()的实现。它的两个子类都是使用它的findClass()
  5. classLoader:提供了loadClass()实现,所有子类均使用它的loadClass()(BootClassLoader除外)。

了解了以上几点之后,下面追一下Dex文件加载的过程,这里要到源码了(Android8.0):

1.第一步:

首先我门加载class的时候都是从loadClass()开始的,上面提到应用程序的类加载器是PathClassLoader, 而它的loadClass使用的是ClassLoader的loadClass() 方法,那就先看一下ClassLoader#loadCalss():

protected Class<?> loadClass(String name, boolean resolve)throws ClassNotFoundException{// First, check if the class has already been loadedClass<?> c = findLoadedClass(name);   // 1if (c == null) {try {if (parent != null) {c = parent.loadClass(name, false);   // 2} else {c = findBootstrapClassOrNull(name);  // 3}} catch (ClassNotFoundException e) {// ClassNotFoundException thrown if class not found// from the non-null parent class loader}if (c == null) {    // 4// If still not found, then invoke findClass in order// to find the class.c = findClass(name);}}return c;}

代码1处:首先从已经加载的class中寻找。看一眼 findLoadedCalss()

 /*** Returns the class with the given <a href="#name">binary name</a> if this* loader has been recorded by the Java virtual machine as an initiating* loader of a class with that <a href="#name">binary name</a>.  Otherwise* <tt>null</tt> is returned.** @param  name*         The <a href="#name">binary name</a> of the class** @return  The <tt>Class</tt> object, or <tt>null</tt> if the class has*          not been loaded** @since  1.1*/protected final Class<?> findLoadedClass(String name) {ClassLoader loader;if (this == BootClassLoader.getInstance())loader = null;elseloader = this;return VMClassLoader.findLoadedClass(loader, name);}

实际上使用了VMClassLoader#findLoadedClass方法,应为这是个native方法,就不追了。可以看出BootClassLoader和非BootClassLoader查找已经加载的class的逻辑是不一样的。

代码2处:父类不为空,调用父类的loadClass(),在父类中重复这一整个的过程。这里的父类指的不是继承关系的父类,而是一个成员属性:

// The parent class loader for delegation// Note: VM hardcoded the offset of this field, thus all new fields// must be added *after* it.private final ClassLoader parent;

注意的是我们自己的类的使用的PathClassLoader里面的parent属性是BootClassLoader.

代码3处:父类为空,去系统类中寻找class, ClassLoader中的实现是返回null,子类中只有BootClassLoader进行了实现。所以只有在加载系统源码的时候进入这里才会有class返回,否则为空。

代码4处:父类和程序本身都没有加载过class,则由自身去加载。

类加载的这种机制java中和android中是一样的,叫做双亲委托机制。首先优先去父类类加载中去进行加载,父类加载到的话就是用父类加载的class,如果父类没有加载到再由自己去加载。

2. 第二步

看一下自身加载时,findClass() 做了什么。上文说过,findClass的实现只有在BootClassLoader和 BaseDexClassLoader中进行实现,这里关心的是BaseDexClassLoader#findClass:

  @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;}

其中pathList的findClass方法:

 /*** 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) {Class<?> clazz = element.findClass(name, definingContext, suppressed);if (clazz != null) {return clazz;}}if (dexElementsSuppressedExceptions != null) {suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));}return null;}

遍历dexElements的数组,在每一个节点中寻找要加载的class,如果找到,停止遍历,立即返回。

首先来看dexElements是什么:

/*** List of dex/resource (class path) elements.* Should be called pathElements, but the Facebook app uses reflection* to modify 'dexElements' (http://b/7726934).*/private Element[] dexElements;

这个数组对应的就是我们apk中的多个dex文件。

总结一下这个过程就是,顺序遍历我们apk中的每一个dex文件,查找我们的class有没有在里面,找到立即返回。

总结一下刚才的源码,各个类的配合流程如下:

基于类加载机制热修复的思路:

  1. 反射获取当前程序的PathClassLoader
  2. 反射获取DexPathClassLoader的pathList属性
  3. 反射获取pathList中的属性dexElements
  4. 把自己的补丁包 patch.dex 转化为 Elements[]数组 pathElements
  5. 将pathElements 插入到  dexElements最前面,得到新的 newElements
  6. 将newElements反射替换原来的dexElements

看起来思路还是挺简单的,下面看一下基于这种思路的 Tinker和 QZone两个框架的特点:

Tinker:

Tinker的特点是有一个DexDiff机制, 需要提供一个Base.apk的基准包,通过这个基准包和当前apk的差异自动生成差分包patch.dex,运行时重新加载差分包。

QZone:

QQ空间基于Dex的分包方案,修复bug之后,放到一个单独的dex文件中。在我理解的QZone和Tinker的区别就是在于生成补丁包的方式: QZone 不会自动生成差分包dex,而是将有bug的class 单独打包成一个 dex, 后面加载我的门生成的dex的过程是类似的,都是进行插队。

抽空写了这篇博客,下一步应该就是手撸代码的环节了,希望能带我飞起。

Android 热修复一(热修复流程原理)相关推荐

  1. 【Android 热修复】热修复原理 ( 修复包 Dex 文件准备 | Dex 优化为 Odex | Dex 文件拷贝 | 源码资源 )

    文章目录 一.修复包 Dex 文件准备 二.Odex 优化 三.Dex 文件拷贝 四. 源码资源 一.修复包 Dex 文件准备 异常代码 : 故意写一个异常代码 , 并执行该代码 , 肯定会崩溃 ; ...

  2. 【Android 热修复】热修复原理 ( 多 Dex 打包机制 | 多 Dex 支持 | Dex 分包设置 | 开发和产品风格设置 | 源码资源 )

    文章目录 一.Dex 打包设置 1.多 Dex 支持 2.Dex 分包设置 3.开发和产品风格设置 ( 非必须 ) 二.完整 build.gradle 配置 1.build.gradle 配置 2.d ...

  3. 【Android 热修复】热修复原理 ( 合并两个 Element[] dexElements | 自定义 Application 加载 Dex 设置 | 源码资源 )

    文章目录 一.合并两个 Element[] dexElements 二. 完整修复包加载工具类 三. 源码资源 一.合并两个 Element[] dexElements 在 [Android 热修复] ...

  4. 【Android 热修复】热修复原理 ( Dex 文件拷贝后续操作 | 外部存储空间权限申请 | 执行效果验证 | 源码资源 )

    文章目录 一.Dex 文件准备 二.外部存储空间权限申请 1.清单文件申请权限 2.动态申请权限 三.文件拷贝 1.文件拷贝 2.执行效果 四. 源码资源 一.Dex 文件准备 在 [Android ...

  5. 【Android 热修复】热修复原理 ( 热修复框架简介 | 将 Java 字节码文件打包到 Dex 文件 )

    文章目录 一. 热修复框架简介 1.类替换 2.so 替换 3.资源替换 4.全平台支持 5.生效时间 6.性能损耗 7.总结 二. 将 Java 字节码文件打包到 Dex 文件 一. 热修复框架简介 ...

  6. Android开发之nuwa热修复

    相信每个开发者在app版本上线后才发现有一个致命性崩溃的bug时,心中是一万只草泥马在奔跑! 每次发现这种bug,都只好立马上个小版本修复.这种体验着实糟糕. 那我们能不能动态加载一小部分代码来修复这 ...

  7. InjectFix原理学习(实现修复加法的热更)

    前期准备 ILRuntime讲的通俗易懂的一篇博客 ILRuntime一篇博客 IL指令大全 InjectFix实现 强烈建议读一下这个文章 查看DLL的IL指令 VS终端输入ildasm 注意这篇文 ...

  8. iOS热修复(热更新)技术预研

    热修复简介 对于iOS应用而言,app store的审核周期可能通常维持在1-2个星期.倘若一个线上的应用出现了一些bug,甚至是致命的崩溃,这时候假如按照苹果的套路乖乖重新发布一个版本,然后静静等待 ...

  9. 浅析“热更新”(热修复)解决方案

    新闻事件背景:11月27日,苹果应用商店集中下架了拼多多.搜狗.科大讯飞.悦跑圈等多家公司的应用产品.科大讯飞和悦跑圈均表示,下架与"热更新"相关.然而,这并不是苹果应用商店第一次 ...

  10. 动态加载、插件化、热部署、热修复(更新)知识汇总

    开发中经常能听到动态加载,插件化,热部署等词,动态加载到底是何方神物,它能实现什么功能,实现原理又如何?动态加载和插件化.热部署又有着什么样的联系呢?下面我们一起来学习吧. 1. 基本知识 1.1 动 ...

最新文章

  1. mysql存储过程查询实例_mysql存储过程查询实例
  2. Python 字典(Dictionary)
  3. 强大的 IDEA 代码生成
  4. 说人话教AI打游戏,Facebook开源迷你版星际争霸,成果登上NeurIPS 2019
  5. PPT 下载 | 神策数据张涛:企业服务客户全生命周期运营三步曲客情诊断 解决方案库...
  6. 一键发布到Maven Central的方法
  7. PHP Cookie和Session
  8. element 方法返回的boolean被当成字符串了_JavaScript 原生对象、属性、方法、事件、事件参数...
  9. comsol临时文件夹中有不支持的字符_文件名中不能包含的字符
  10. 马斯克加入推特董事会引发员工担忧:可能改变审查规则
  11. 【更新汇总】FastReport系列更新|附下载
  12. OSPF NSSA 默认路由的问题
  13. 饿了么是视障者非常喜欢的APP,你们要加油哦!
  14. CHI 2016 2017 Paper Shared Gaze for Remote Collaboration
  15. java实现账号登陆界面_java用户登录界面的代码
  16. 【秋招毕业】自由奔赴的行者2021年终总结
  17. python遗传算法解决分配问题
  18. IT从业者创业公司生存指南:创业后记 ---- 百感交集的过来人
  19. hexo写博客时怎么插入图片
  20. matlab由自相关函数求功率谱密度,转:matlab求功率谱密度代码实例

热门文章

  1. source insight使用方法简介
  2. 计算机二级:公共基础部分
  3. linux设备模型——总线,驱动,设备间的关系
  4. out of synch
  5. 了解源代码管理工具——Github
  6. 深圳“托育”放大招!政府出钱帮你带娃啦!
  7. ModelSim illegal reference to net “***“ 报错问题解决
  8. 谷粒商城简介(1~5集)
  9. uniapp-公众号微信授权
  10. 094 chrome浏览页面常用快捷键