1.1 什么是热修复

对于广大的移动开发者而言,发版更新是最为寻常不过的事了。然而,如果你 发现刚发出去的包有紧急的BUG需要修复,那你就必须需要经过下面这样的流程:

这就是传统的更新流程,步骤十分繁琐。总的来说,传统流程存在这几大弊端:

  • 重新发布版本代价太大

  • 用户下载安装成本太高

  • BUG 修复不及时,用户体验太差

相应的,许多开发者找到了比较合适的解决办法。

  1. Hybrid 方案。也就是把需要经常变更的业务逻辑以 H5 的方式独立出来。而这种方案, 需要传统的 java 开发者学习前端语言,不仅增加了学习成本,而且还要对原先的逻辑 进行合适的抽象和转换。并且,对于无法转为 H5 形式的代码仍旧是无法修复的。

  2. 使用插件化方案来解决问题,像 Atlas 或者 DroidPlugin 方案。 而这类方式,移植成本非常高,还要学习整套插件化工具,对原先老代码的改造。

于是,热修复技术应运而生了。

1.2 技术沉淀

阿里系:

  • Dexposed:基于Xposed改进,针对Android Dalvik虚拟机运行的Java Method Hook技术,但无法兼容Android5.0以后的虚拟机

  • Andfix:也是一种底层替换的方案,做到了 Dalvik 和 ART 的兼容

  • Hotfix:结合实际工程中的使用Andfix的经验,推出阿里百川Hotfix,但只提供了代码层面的修复,对于资源和so的修复还未实现

  • Sophix:2017年6月推出Sophix,打破了各家纷争的局面,在代码修复,资源修复,so修复方面,都做到了业界领先

其他著名的热修复,但是各自有各自的局限性,补丁过大,效率低下,不够稳定,用起来繁琐:

  • 腾讯 QQ 空间的超级补丁

  • 微信的 Tinker

  • 饿了么的 Amigo

  • 美团的 Robust

1.3 详细比较

Sophix和Tinker与Amigo的比较:

各项指标都占优,唯一不支持的就是四大组件的修复

1.4 技术概览

1.4.1 设计理念

Sophix 的设计理念,就是非侵入性

  • 最终的实现只有两个生成的新旧 apk,唯一要做的就是初始化和请求补丁两行代码

  • 不会侵入 apk 的 build 流程中

  • 不改变任何打包组件

  • 不插入任何 AOP 代码

1.4.2 代码修复

代码修复有两大主要方案,一种是阿里系的底层替换方案,另一种是腾讯系的类加载方案。

两种方案各有优劣:

  • 底层替换方案限制颇多,但时效性最好,加载轻快,立即见效。

  • 类加载方案时效性差,需要重新冷启动才能见效,但修复范围广,限制少。

底层替换方案

底层替换方案是在已经加载了的类中直接替换掉原有方法,是在原来类的基础上进行修改的。因而无法实现对与原有类进行方法和字段的增减,因为这样将破坏原有类的结构。

一旦补丁类中出现了方法的增加和减少,就会导致这个类以及整个 Dex 的方法数的变化。方法数的变化伴随着方法索引的变化,这样在访问方法时就无法正常地索引到正确的方法了。如果字段发生了增加和减少,和方法变化的情况一样,所有字段 的索引都会发生变化。并且更严重的问题是,如果在程序运行中间某个类突然增加了 —个字段,那么对于原先已经产生的这个类的实例,它们还是原来的结构,这是无法改变的。而新方法使用到这些老的实例对象时,访问新增字段就会产生不可预期 的结果。

这是这类方案的固有限制,而底层替换方案最为人诟病的地方,在于底层替换的不稳定性。

通过对代码的底层替换原理重新进行了深入思考,从克服其限制和兼容性入 手,以一种更加优雅的替换思路,实现了即时生效的代码热修复。

采用一种无视底层具体结构的替换方式,这种方式不仅解决了兼容性问题,并且由于忽略了底层ArtMethod结构的差异,对于所有的Android版本都不 再需要区分,代码量大大减少。即使以后的Android版本不断修改ArtMethod的 成员,只要保证ArtMethod数组仍是以线性结构排列,就能直接适用于将来的 Android 8.0、9.0等新版本,无需再针对新的系统版本进行适配了。

类加载方案

类加载方案的原理是在 app 重新启动后让 Classloader 去加载新的类。因为在 app运行到一半的时候,所有需要发生变更的类已经被加载过了,在 Android 上是无法对一个类进行卸载的。如果不重启,原来的类还在虚拟机中,就无法加载新类。 因此,只有在下次重启的时候,在还没走到业务逻辑之前抢先加载补丁中的新类,这样后续访问这个类时,就会Resolve为新类。从而达到热修复的目的。

再来看看腾讯系三大类加载方案的实现原理。

  1. QQ 空间方案会侵入打包流程,并 且为了 hack 添加一些无用的信息,实现起来很不优雅。

  2. QFix 的方案,需要获取 底层虚拟机的函数,不够稳定可靠,并且有个比较大的问题是无法新增public函数。

  3. 微信的 Tinker 方案是完整的全量 dex 加载,并且可谓是将补丁合成做到了极致。Tinker 的合成方案,是从 dex 的方法和指令维度进行全量合成,整个过程都是自己研发的。虽然可以很大地节省空间,但由于对dex内容的比较粒度过细,实现较为复杂,性能消耗比较严重。实际 上,dex 的大小占整个apk的比例是比较低的,一个 app 里面的dex文件大小并不 是主要部分,而占空间大的主要还是资源文件。因此,Tinker 方案的时空代价转换的性价比不高。

dex 比较的最佳粒度,应该是在类的维度。它既不像方法和指令维度那样的细微,也不像 bsbiff 比较那般的粗糙。在类的维度,可以达到时间和空间平衡的最 佳效果。基于这个准则,另辟蹊径,实现了一种完全不同的全量dex替换方案。

  • 直接利用 Android 原先的类查找和合成机制,快速合成新的全量 dex。这么一来,既不需要处理合成时方法数超过的情况,对于 dex 的结构也不用进行破坏性重构。

  • 重新编排了包中dex的顺序。虚拟机查找类的时候,会优先找到 classes.dex 中的类,然后才是 classes2.dex、classes3.dex,也可以看做是 dex 文件级别的类插桩方案。这个方式对旧包与补丁包中 classes.dex 的顺 序进行了打破与重组,最终使得系统可以自然地识别到这个顺序,以实现类覆盖的目 的。大大减少合成补丁的开销。

双剑合璧

既然底层替换方案和类加载方案各有其优点,把他们联合起来不是最好的选择吗?

Sophix的代码修复体系正是同时涵盖了这两种方案。两种方案的结合,可以实现优势互补,完全兼顾的作用,可以灵活地根据实际情况自动切换。

在补丁生成阶段,补丁工具会根据实际代码变动情况进行自动选择,

  • 针对小修改,在底层替换方案限制范围内的,就直接采用底层替换修复吗,这样可以做到代码修复即时生效。

  • 对于代码修改超出底层替换限制的,会使用类加载替换,这样虽然及时性没那么好,但总归可以达到热修复的目的。

  • 运行时阶段,Sophix 还会再判断所运行的机型是否支持热修复,这样即使补丁支持热修复,但由于机型底层虚拟机构造不支持,还是会走类加载修复,从而达到最好的兼容性。

1.4.3 资源修复

目前市面上的很多资源热修复方案基本上都是参考了 Instant Run 的实现。实际 上,Instant Run 的推出正是推动这次热修复浪潮的主因,各家热修复方案,在代码、 资源等方面的实现,很大程度上地参考了 Instant Run的代码,而资源修复方案正是 被拿来用到最多的地方。

简要说来,Instant Run 中的资源热修复分为两步:

  1. 构造一个新的AssetManager,并通过反射调用 addAssetPath,把这个完 整的新资源包加入到 AssetManager 中。这样就得到了一个含有所有新资源 的 AssetManager。

  2. 找到所有之前引用到原有AssetManager的地方,通过反射,把引用处替换为 AssetManager 。

新的实现方式:构造了一个 package id 为 0x66 的资源包,这个包里只包含改变了的资源项,然后直接在原有 AssetManager 中 addAssetPath 这个包就可以了。由于补丁包的 package id 为 0x66,不与目前已经加载的 0x7f 冲突,因此直接加入到已有的 AssetManager 中就可以直接使用了。

补丁包里面的资源,只包含原有包里面没有而新的包里面有的新增资源,以及原有内容发生了改变的资源。并且,我们采用了更加优雅的替换方式,直接在原有的 AssetManager 对象上进行析构和重构,这样所有原先对 AssetManager对象的引用是没有发生改变的,所以就不需要像Instant Run 那样 进行繁琐的修改了。

可以说,我们的资源修复方案,优越性超过了 Google官方的Instant Run方 案。整个资源替换的方案优势在于:

  • 不修改 AssetManager 的引用处,替换更快更完全。(对比 Instanat Run 以 及所有 copycat 的实现)

  • 不必下发完整包,补丁包中只包含有变动的资源。(对比 Instanat Runs Amigo 等方式的实现)

  • 不需要在运行时合成完整包。不占用运行时计算和内存资源。(对比 Tinker 的实现)

1.4.4 SO库修复

SO 库的修复本质上是对 native 方法的修复和替换。

我们采用的是类似类修复反射注入方式。把补丁 so 库的路径插入到 nativeLi- braryDirectories 数组的最前面,就能够达到加载 so 库的时候是补丁 so 库,而不是原来 so 库的目录,从而达到修复的目的。

采用这种方案,完全由 Sophix 在启动期间反射注入 patch 中的 so 库。对开发者依然是透明的。不用像某些其他方案需要手动替换系统的 System.load 来实现替 换目的。

1.5 本章小结

本章介绍了热修复技术的主要使用场景和为业界带来的变化。详细说明了阿里巴巴推出的热修复解决方案 Sophix 的由来,同时与其他各大主流方案进行了比较。另外,粗略介绍了热修复所涉及的各个方面,并引导概述后续各个章节。

参考文章

深入探索Android热修复技术原理读书笔记——第一章:热修复技术介绍

深入探索Android热修复技术原理[book]

深入探索Android热修复技术原理读书笔记 —— 热修复技术介绍的更多相关文章

  1. 深入探索Android热修复技术原理读书笔记 —— 代码热修复技术

    在前一篇文章 深入探索Android热修复技术原理读书笔记 -- 热修复技术介绍中,对热修复技术进行了介绍,下面将详细介绍其中的代码修复技术. 1 底层热替换原理 在各种 Android 热修复方案中 ...

  2. 深入探索Android热修复技术原理读书笔记 —— 资源热修复技术

    该系列文章: 深入探索Android热修复技术原理读书笔记 -- 热修复技术介绍 深入探索Android热修复技术原理读书笔记 -- 代码热修复技术 1 普遍的实现方式 Android资源的热修复,就 ...

  3. 深入探索Android热修复技术原理读书笔记 —— so库热修复技术

    热修复系列文章: 深入探索Android热修复技术原理读书笔记 -- 热修复技术介绍 深入探索Android热修复技术原理读书笔记 -- 代码热修复技术 深入探索Android热修复技术原理读书笔记 ...

  4. <<操作系统精髓与设计原理>>读书笔记(一) 并发性:互斥与同步(1)

    <<操作系统精髓与设计原理>>读书笔记(一) 并发性:互斥与同步 并发问题是所有问题的基础,也是操作系统设计的基础.并发包括很多设计问题,其中有进程间通信,资源共享与竞争,多个 ...

  5. Struts2技术内幕 读书笔记一 框架的本质

    本读书笔记系列,主要针对陆舟所著<<Struts2技术内幕 深入解析Strtus2架构设计与实现原理>>一书.笔记中所用的图片若无特殊说明,就都取自书中,特此声明. 什么是框架 ...

  6. 《Android源代码设计模式解析》读书笔记——Android中你应该知道的设计模式

    断断续续的,<Android源代码设计模式解析>也看了一遍.书中提到了非常多的设计模式.可是有部分在开发中见到的几率非常小,所以掌握不了也没有太大影响. 我认为这本书的最大价值有两点,一个 ...

  7. LOMA280保险原理读书笔记

    LOMA是国际金融保险管理学院(Life Office Management Association)的英文简称.国际金融保险管理学院是一个保险和金融服务机构的国际组织,它的创建目的是为了促进信息交流 ...

  8. Android驱动开发5-8章读书笔记

    Android驱动开发读书笔记                                                              第五章 S5PV210是一款32位处理器,具有 ...

  9. Struts2技术内幕 读书笔记三 表示层的困惑

    表示层能有什么疑惑?很简单,我们暂时忘记所有的框架,就写一个注册的servlet来看看. index.jsp <form id="form1" name="form ...

随机推荐

  1. How DRI and DRM Work

    How DRI and DRM Work Introduction This page is intended as an introduction to what DRI and DRM are, ...

  2. vue 递归调用组件出错

    报错信息: Avoid mutating an injected value directly since the changes will be overwritten whenever the p ...

  3. Git:本地仓库管理

    git log:查看 commit 提交历史 git log --pretty=oneline:简化log输出内容 git reflog:查看每一次命令的历史记录 版本回退 git reset HEA ...

  4. 技术基础 | 在Apache Cassandra中改变VNodes数量的影响

    Apache Cassandra中num_tokens的默认值在4.0版本中将会有变化!这看起来好像只是在CHANGES.txt文件中做了个小小的改动,但实际上这个改动将会对集群的日常运维有着深远的影 ...

  5. jQuery学习笔记(1) 初识jQuery

    目录 目录 引用 注意 HelloWorldHelloWorld! jQueryjQuery对象和DOMDOM对象的相互转换 冲突的解决 引用 本地文件引用: <script src=" ...

  6. Masterwoker模式

    1 public class Task { 2 3 private int id; 4 private int price ; 5 public int getId() { 6 return id; ...

  7. P1048_采药(JAVA语言)

    思路:动态规划的背包问题.把时间看作重量,转换为01背包问题求解. 题目描述 辰辰是个天资聪颖的孩子,他的梦想是成为世界上最伟大的医师.为此,他想拜附近最有威望的医师为师.医师为了判断他的资质,给他出 ...

  8. 1 [main] DEBUG Sigar - no sigar-amd64-winnt.dll in java.library.path org.hyperic.sigar.SigarException: no sigar-amd64-winnt.dll in java.library.path

    github上一个java项目,在myeclipse中运行正常,生成jar后,运行报错: 1 [main] DEBUG Sigar - no sigar-amd64-winnt.dll in java ...

  9. 第2课:操作系统网络配置【DevOps基础培训】

    第2课:操作系统网络配置 --DevOps基础培训 1. DNS配置 1.1 什么是DNS? 域名系统(英文:Domain Name System,缩写:DNS)是互联网的一项服务.它作为将域名和IP ...

  10. Typora标题自动编号+设定快捷键技巧

    Typora标题自动编号 提示:要了解将这些CSS片段放在哪里,请参阅添加自定义CSS. 打开Typora偏好设置,打开主题文件夹,在主题文件夹中创建base.user.css文件,放置以下内容,则T ...