前言

现在线上的BUG一直是令很多Android工程师所发愁的问题,可能就是那么几行代码,会让自己所研发的APP损失惨重,所以,热修复完美的解决了这些问题。下面就是我整理总结的一些热修复知识点和大厂热修复的一些相关资料。

一、什么是热修复?

热修复就是一个APP上线发布以后,发现自身存在很多BUG,想要修复这些BUG,但是如果重新推出一个版本、发布、再供用户下载,那样所用的时间就太久了,不利用户体验,所以热修复就出来了,他可以在用户所下载的APP里发布一个插件,他可以在不发布新版本的前提下,修复APP的BUG,这就叫热修复

二、热修复的优势

三、热修复机制

dexElements的数组

<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="dart" cid="n259" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;"> /*** 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;</pre>

热修复就是利用dexElements的顺序来做文章,当一个补丁的patch.dex放到了dexElements的第一位,那么当加载一个bug类时,发现在patch.dex中,则直接加载这个类,原来的bug类可能就被覆盖了

看下PathClassLoader代码

<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="java" cid="n262" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">public class PathClassLoader extends BaseDexClassLoader {public PathClassLoader(String dexPath, ClassLoader parent) {super(dexPath, null, null, parent);}public PathClassLoader(String dexPath, String libraryPath,ClassLoader parent) {super(dexPath, null, libraryPath, parent);}
} </pre>

DexClassLoader代码

<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="dart" cid="n264" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">public class DexClassLoader extends BaseDexClassLoader {public DexClassLoader(String dexPath, String optimizedDirectory, String libraryPath, ClassLoader parent) {super(dexPath, new File(optimizedDirectory), libraryPath, parent);}
}</pre>

两个ClassLoader就两三行代码,只是调用了父类的构造函数.

<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="java" cid="n266" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">public class BaseDexClassLoader extends ClassLoader {private final DexPathList pathList;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;}</pre>

在BaseDexClassLoader 构造函数中创建一个DexPathList类的实例,这个DexPathList的构造函数会创建一个dexElements 数组

<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="tsx" cid="n268" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">public DexPathList(ClassLoader definingContext, String dexPath, String libraryPath, File optimizedDirectory) {... this.definingContext = definingContext;ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>();//创建一个数组this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory, suppressedExceptions);... }</pre>

然后BaseDexClassLoader 重写了findClass方法,调用了pathList.findClass,跳到DexPathList类中.

<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="dart" cid="n270" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">/* package */final class DexPathList {...public Class findClass(String name, List<Throwable> suppressed) {//遍历该数组for (Element element : dexElements) {//初始化DexFileDexFile dex = element.dexFile;if (dex != null) {//调用DexFile类的loadClassBinaryName方法返回Class实例Class clazz = dex.loadClassBinaryName(name, definingContext, suppressed);if (clazz != null) {return clazz;}}} return null;}...
} </pre>

会遍历这个数组,然后初始化DexFile,如果DexFile不为空那么调用DexFile类的loadClassBinaryName方法返回Class实例,归纳上面的话就是:ClassLoader会遍历这个数组,然后加载这个数组中的dex文件,而ClassLoader在加载到正确的类之后,就不会再去加载有Bug的那个类了,我们把这个正确的类放在Dex文件中,让这个Dex文件排在dexElements数组前面即可。

四、常见的几个热修复框架的对比

热修复框架的种类繁多,按照公司团队划分主要有以下几种:

类别 成员
阿里系 AndFix、Dexposed、阿里百川、Sophix
腾讯系 微信的Tinker、QQ空间的超级补丁、手机QQ的QFix
知名公司 美团的Robust、饿了么的Amigo
其他 RocooFix、Nuwa、AnoleFix

虽然热修复框架很多,但热修复框架的核心技术主要有三类,分别是 代码修复、资源修复和动态链接库修复,其中每个核心技术又有很多不同的技术方案,每个技术方案又有不同的实现,另外这些热修复框架仍在不断的更新迭代中,可见热修复框架的技术实现是繁多可变的。

部分热修复框架的对比如下表所示:

特性 AndFix Tinker/Amigo QQ空间 Robust/Aceso
即时生效
方法替换
类替换
类结构修改
资源替换
so替换
支持gradle
支持ART
支持Android7.0

我们可以根据上表和具体业务来选择合适的热修复框架,当然上表的信息很难做到完全准确,因为部分的热修复框架还在不断更新迭代。

五、技术原理及特点

5.1 阿里Dexposed – native

原理:

  • 直接在native层进行方法的结构体信息对换,从而实现完美的方法新旧替换,从而实现热修复功能

  • 基于开源框架Xposed实现,是一种AOP解决方案

  • 只Hook App本身的进程,不需要Root权限

优点:

  • 即时生效

  • 不需要任何编译器的插桩或者代码改写,对正常运行不引入任何性能开销。这是AspectJ之类的框架没法比拟的优势;

  • 对所改写方法的性能开销也极低(微秒级),基本可以忽略不计;

  • 从工程的角度来看,热补丁仅仅是牛刀小试,它真正的威力在于『线上调试』;

  • 基于Xposed原理实现的AOP不仅可以hook自己的代码,还可以hook同进程的Android SDK代码,这也就可以让我们有能力在App中填上Google自己挖的坑。

缺点:

  • Dalvik上近乎完美,不支持ART(需要另外的实现方式),所以5.0以上不能用了;

  • 最大挑战在于稳定性与兼容性,而且native异常排查难度更高;

  • 由于无法增加变量与类等限制,无法做到功能发布级别;

5.2 阿里AndFix – native

原理:

  • 与Dexposed一样都基于开源框架Xposed实现,是一种AOP解决方案

优点:

  • 即时生效

  • 支持dalvik和art(AndFix supports Android version from 2.3 to 7.0, both ARM and X86 architecture, both Dalvik and ART runtime, both 32bit and 64bit.)

  • 与Dexposed框架相比AndFix框架更加轻便好用,在进行热修复的过程中更加方便了

缺点:

  • 面临稳定性与兼容性问题

  • AndFix不支持新增方法,新增类,新增field等

5.3 QQ空间–Dex插桩方案

原理:

  • 原理是Hook了ClassLoader.pathList.dexElements[]。因为ClassLoader的findClass是通过遍历dexElements[]中的dex来寻找类的。当然为了支持4.x的机型,需要打包的时候进行插桩。

  • 越靠前的Dex优先被系统使用,基于类级别的修复

优点:

  • 不需要考虑对dalvik虚拟机和art虚拟机做适配

  • 代码是非侵入式的,对apk体积影响不大

缺点:

  • 需要下次启动才会生效

  • 最大挑战在于性能,即Dalvik平台存在插桩导致的性能损耗,Art平台由于地址偏移问题导致补丁包可能过大的问题

  • 虚拟机在安装期间为类打上CLASS_ISPREVERIFIED标志是为了提高性能的,我们强制防止类被打上标志是否会影响性能?这里我们会做一下更加详细的性能测试.但是在大项目中拆分dex的问题已经比较严重,很多类都没有被打上这个标志。

5.4 美团Robust – Instant Run 热插拔

原理:

  • Robust插件对每个产品代码的每个函数都在编译打包阶段自动的插入了一段代码,插入过程对业务开发是完全透明

  • 编译打包阶段自动为每个class都增加了一个类型为ChangeQuickRedirect的静态成员,而在每个方法前都插入了使用changeQuickRedirect相关的逻辑,当 changeQuickRedirect不为null时,可能会执行到accessDispatch从而替换掉之前老的逻辑,达到fix的目的。

优点:

  • 几乎不会影响性能(方法调用,冷启动)

  • 支持Android2.3-8.x版本

  • 高兼容性(Robust只是在正常的使用DexClassLoader)、高稳定性,修复成功率高达99.9%

  • 补丁实时生效,不需要重新启动

  • 支持方法级别的修复,包括静态方法

  • 支持增加方法和类

  • 支持ProGuard的混淆、内联、优化等操作

缺点:

  • 代码是侵入式的,会在原有的类中加入相关代码

  • so和资源的替换暂时不支持

  • 会增大apk的体积,平均一个函数会比原来增加17.47个字节,10万个函数会增加1.67M。

  • 会增加少量方法数,使用了Robust插件后,原来能被ProGuard内联的函数不能被内联了

5.5 微信Tinker

原理:

  • 服务端做dex差量,将差量包下发到客户端,在ART模式的机型上本地跟原apk中的classes.dex做merge,merge成为一个新的merge.dex后将merge.dex插入pathClassLoader的dexElement,原理类同Q-Zone,为了实现差量包的最小化,Tinker自研了DexDiff/DexMerge算法。Tinker还支持资源和So包的更新,So补丁包使用BsDiff来生成,资源补丁包直接使用文件md5对比来生成,针对资源比较大的(默认大于100KB属于大文件)会使用BsDiff来对文件生成差量补丁。

优点:

  • 支持动态下发代码

  • 支持替换So库以及资源

缺点:

  • 不能即时生效,需要下次启动

Tinker已知问题:

  • Tinker不支持修改AndroidManifest.xml,Tinker不支持新增四大组件(1.9.0支持新增非export的Activity);

  • 由于Google Play的开发者条款限制,不建议在GP渠道动态更新代码;

  • 在Android N上,补丁对应用启动时间有轻微的影响;

  • 不支持部分三星android-21机型,加载补丁时会主动抛出"TinkerRuntimeException:checkDexInstall failed";

  • 对于资源替换,不支持修改remoteView。例如transition动画,notification icon以及桌面图标。

Tinker性能痛点:

  • Dex合并内存消耗在vm head上,容易OOM,最后导致合并失败。

  • 如果本身app占用内存已经比较高,可能容易导致app本系统杀掉。

5.6 阿里Sophix

优化Andfix(突破底层结构差异,解决稳定性问题):

Andfix底层ArtMethod结构时采用内部变量一一替换,倒是这个各个厂商是会修改的,所以兼容性不好。

Sophix改变了一下思路,采用整体替换方法结构,忽略底层实现,从而解决兼容稳定性问题。

突破QQ和Tinker的缺陷

原理

六、热修复需要解决的难点

热修复不同于插件化,不需要考虑各种组件的生命周期,唯一需要考虑的就是如何能将问题的方法/类/资源/so 替换为补丁中的新方法/类/资源/so,其中最重要的是方法和类的替换,所以有不少热修复框架只做了方法和类的替换,而没有对资源和 so 进行处理。热修复框架普遍存在一个问题: 虽然不用安装新版本的安装包同样可以修复bug,但是如果本地下载好的补丁包被删除了,那么之前bug就会重新!因为热修复不是合拼生成新的apk,而是 动态加载修复bug的那部分代码。换句话说修复bug的代码是存放在补丁包里的,删除补丁包,修复bug的代码也就不存在了.之前bug也就重新出来了。

总结

现在的热修复的技术可以说是百花齐放了,很多大型的公司都有自己完整的热修复技术框架,但是想要深入了解热修复,就需要先去了解其中的一些机制,很多机制需要庞大的知识贮备才能进行深入理解,当然Android Framwork的实现细节是非常重要的

热修复不是简单的客户端SDK,它还包含了安全机制和服务端的控制逻辑,整条链路也不是短时间可以快速完成的。所以需要我们深入了解才能更好的去理解

说到这里相信大家不难看出; 想要众多 Android 开发者中有着自己的一席之地,那就必须要对 Android FrameWork 有着深入的理解,不然无论你是继续内卷,还是想要进行转型,都难以突破这一界限

但大多的 Android 开发者对于 Android FrameWork 其实并没有对其有着过多的了解,更别说深入理解了; 所以想要成为一个真正的 Android 高级工程师, FrameWork 一定是你必不可缺的一门知识

在近段时间我对 Framework 相关的知识点进行了收集和整理,将其汇总成了PDF文档,希望可以给大家的技术提升提供一些方向

Framework学习大纲

有想要学习Framework的同学 ,可以顺手给我点赞评论转发分享一下

由于文章有着篇幅限制,笔记的内容过多,思虑过后,暂在文章中放入知识点图片

需要完整PDF的同学: 可以私信发送 “进阶” 或 “笔记” 即可 免费获取

一、Handlar 相关知识

二、Avtivity 相关

三、Frageant 相关

四、Service 相关

五、Android布局优化之ViewStub、include、 merge

获取方式私信发送 “进阶” 或 “笔记” 即可 免费获取

技术是无止境的,你需要对自己提交的每一行代码、使用的每一个工具负责,不断挖掘其底层原理,才能使自己的技术升华到更高的层面

Android 架构师之路还很漫长,与君共勉

修复APP的BUG,热修复的知识点和大厂的相关资料汇总相关推荐

  1. Alibaba-AndFix Bug热修复框架原理及源码解析

    小憩之后,继续为你解读AndFix热修复框架,呵呵. 上一篇Alibaba-AndFix Bug热修复框架的使用已经介绍了AndFix的使用,这篇主要介绍AndFix原理以及源码解析. AndFix原 ...

  2. 热修复系列之一----Android 热修复原理篇及几大方案比较

    热修复说白了就是"即时无感打补丁",比如你们公司上线一个app,用户反应有重大bug,需要紧急修复.2015年以来,Android开发领域里对热修复技术的讨论和分享越来越多,同时也 ...

  3. 服务器中的热修复怎么做,Android 热修复(全网最简单的热修复讲解)

    首先!我们抛开网上的热修复框架不谈,我们来通过原理手动实现一个热修复工具,在撸码之前我们先通过一张图来了解热修复的流程. Android热修复 ACCCB328-AF5C-4BD9-AD08-6F7D ...

  4. Alibaba-AndFix Bug热修复框架的使用

    目录(?)[+] AndFix 这篇主要介绍alibaba的AndFix项目的使用,下一篇介绍 AndFix原理及源码解析. Github:https://github.com/alibaba/And ...

  5. Android App 线上热修复方案

    热修复一词恐怕最早应用在微软.为了巩固其windows系统和office的市场占有率,微软开发并维护了一套线上修复方案,用于修复漏洞及特定问题(LDR),避免延续到发版解决(GDR),详见HotFix ...

  6. Android App 线上热修复方案Xposed

    热修复一词恐怕最早应用在微软.为了巩固其windows系统和office的市场占有率,微软开发并维护了一套线上修复方案,用于修复漏洞及特定问题(LDR),避免延续到发版解决(GDR),详见HotFix ...

  7. android热修复技术tinker,Android热修复方案第一弹——Tinker篇

    背景 一款App的正常开发流程应该是这样的:新版本上线-->用户安装-->发现Bug-->紧急修复-->重新发布新版本-->提示用户安装更新,从表面上看这样的开发流程顺理 ...

  8. 美团热修复 Android适配,美团热修复Robust用法和实践

    今天说一下Android热修复的问题. 在之前的项目中一直都是使用 andFix做热修复,而且一直用的比较稳定.突然某天在新的项目上使用的时候发现7.0以上的手机一直没能成功,最后发现是兼容性的问题. ...

  9. Android热修复——深入剖析AndFix热修复及自己动手实现

    前言 去年写过一篇热修复的文章,那时候刚开始接触,照猫画虎画的还算比较成功.但是那种修复需要重新启动APP,也就是在JAVA层实现的热修复.我们知道目前Android主流的修复还有在Native层实现 ...

  10. Android热修复Java类_Android 热修复(一)

    名词: dex:java文件编译class 然后生成 dex文件在Android上运行: 1.dex分包: 2.找出出现问题的dex文件进行替换操作 3.下载dex文件,静默替换有问题的dex文件,进 ...

最新文章

  1. python 实用程序代码_【转】python常用工具代码
  2. IOS -- base64编码
  3. loadrunner——win7+LR11配置
  4. Python经典面试题100道(附PDF下载地址)
  5. java listmode_java中图形界面ListModel的用法?方法如何调用?
  6. webpack打包的两种方式
  7. php 实时更新内容_亿级视频内容如何实时更新?优酷视频背后的技术揭秘
  8. 12c集群日志位置_大数据系列教程006-开启日志聚合功能
  9. HDU 1286 找新朋友 (欧拉函数)
  10. php.js 文件下载,使用JavaScript开始下载文件
  11. c语言 465串口编程,用C语言编写串口程序
  12. 区块链开发(一)搭建基于以太坊的私有链环境
  13. 学院后勤报修系统php_企业智能故障报修系统,助你轻松解决设备维修难题
  14. 海思35系列型号排行_11月手机性能排行榜:小米10至尊纪念版排名第三
  15. php 开发一元夺宝插件,yiyuanyungou 一元云购商城源码,商用 ci框架开发,带指定中奖插件 Other systems 其他 249万源代码下载- www.pudn.com...
  16. 电脑控制手机 易语言实现颜色识别功能
  17. 重复代码检查工具simian的基本用法
  18. 数学 计算机类论文题目,数学计算机论文题目范文 数学计算机论文标题如何定...
  19. 有物不知其数,三三数之剩二,五五数之剩三,七七数之剩二。问物几何?
  20. 敏捷团队的病与药:阿里健康医药B2B团队敏捷转型手记

热门文章

  1. Spring常用注解含义
  2. matlab 文本分析工具,MATLAB,Simulink. - Text Analytics , 文本分析工具箱-鈦思科技
  3. macbook使用共享屏幕实现VNC远程控制
  4. Java毕设项目银行贷款管理系统计算机(附源码+系统+数据库+LW)
  5. List集合排序的几种方式
  6. (十一)GDBdebug调试技术——malloc()和free()发生故障
  7. web前端软件VS-Code-的下载和安装
  8. 完美破解视频VIP?这款APP被判赔5000万
  9. USBoot /WinHex恢复故障U盘的数据文件
  10. tx关于机器人的律师函_史陶比尔TX40机器人说明书.pdf