程序员人生 网站导航

浅谈主流热修复技术

栏目:综合技术时间:2016-11-28 13:43:52

热修复

热修复作为当下热门的技术,在业界内比较著名的有阿里巴巴的AndFix、Dexposed,腾讯QQ空间的超级补钉技术和微信的Tinker。最近阿里百川推出的HotFix热修复服务就基于AndFix技术,定位于线上紧急BUG的即时修复,所以AndFix技术这块我们重点分析阿里百川HotFix。下面,我们就分别介绍QQ空间超级热补钉技术和微信的Tinker和阿里百川HotFix技术

qq空间超级补钉方案

超级补钉技术基于DEX分包方案,使用了多DEX加载的原理,大致的进程就是:把BUG方法修复以后,放到1个单独的DEX里,插入到dexElements数组的最前面,让虚拟机去加载修复完后的方法。
这里写图片描述

当patch.dex中包括Test.class时就会优先加载,在后续的DEX中遇到Test.class的话就会直接返回而不去加载,这样就到达了修复的目的。

但是有1个问题是,当两个调用关系的类不在同1个DEX时,就会产生异常报错。我们知道,在APK安装时,虚拟机需要将classes.dex优化成odex文件,然后才会履行。在这个进程中,会进行类的verify操作,如果调用关系的类都在同1个DEX中的话就会被打上CLASS_ISPREVERIFIED的标志,然后才会写入odex文件。

所以,为了可以正常的进行打补钉修复,必须避免类被打上CLASS_ISPREVERIFIED标志,具体的做法就是单独放1个类在另外DEX中,让其他类调用。

我们来逆向手机QQ空间APK看1下具体的实现:

先进入程序入口QZoneRealApplication,在attachBaseContext中进行了两步操作:修复CLASS_ISPREVERIFIED标志致使的unexpected DEX problem异常、加载修复的DEX。
这里写图片描述

  1. 修复unexpectedDEX problem异常

    可以看到,这里是要加载1个libs目录下的dalvikhack.jar。在项目的assets/libs找到该文件,解压得到classes.dex文件,逆向打开该DEX文件
    这里写图片描述
    通过不同的DEX加载进来,然后在每个类的构造方法中援用其他dex中的唯1类AnitLazyLoad,避免类被打上CLASS_ISPREVERIFIED标志
    这里写图片描述
    在无修复的情况下,将DO_VERIFY_CLASSES设置为false,提高性能。只有在需要修复的时候,才设置为true
    这里写图片描述
    至于如何加载进来,与接下来第2个步骤基本相同
  2. 加载修复的DEX
    从loadPatchDex()方法进入,经过几次跳转,到达核心的代码段,SystemClassLoaderInjector.c()。由于进行了混淆和屡次方法的跳转,因而将核心代码段做了以下整理
    这里写图片描述
    修复的步骤为:
    • 可以看出是通过获得到当前利用的Classloader,即为BaseDexClassloader
    • 通过反射获得到他的DexPathList属性对象pathList
    • 通过反射调用pathList的dexElements方法把patch.dex转化为Element[]
    • 两个Element[]进行合并,把patch.dex放到最前面去
    • 加载Element[],到达修复目的
      这里写图片描述

优势
1. 没有合成整包(和微信Tinker比起来),产物比较小,比较灵活
2. 可以实现类替换,兼容性高。(某些3星手机不起作用)

不足
1. 不支持即时生效,必须通太重启才能生效
2. 实现修复这个进程,必须在利用中加入两个dex!dalvikhack.dex中只有1个类,对性能影响不大,但是对patch.dex来讲,修复的类到了1定数量,就需要花很多的时间加载。对手淘这类航母级利用来讲,启动耗时增加2s以上是不能够接受的事
3. 在ART模式下,如果类修改了结构,就会出现内存错乱的问题。为了解决这个问题,就必须把所有相干的调用类、父类子类等等全部加载到patch.dex中,致使补钉包异常的大,进1步增加利用启动加载的时候,耗时更加严重

微信Tinker

微信针对QQ空间超级补钉技术的不足提出了1个提供DEX差量包,整体替换DEX的方案。主要的原理是与QQ空间超级补钉技术基本相同,区分在于不再将patch.dex增加到elements数组中,而是差量的方式给出patch.dex,然后将patch.dex与利用的classes.dex合并,然后整体替换掉旧的DEX,到达修复的目的

这里写图片描述

  • 我们来逆向微信APK看1下具体的实现:
    先找到利用入口TinkerApplication,在onBaseContextAttached()调用了loadTinker()

这里写图片描述
* 进入TinkerLoader的tryLoad()方法中

这里写图片描述
* 从方法名可以预感,在tryLoadPatchFilesInternal()中尝试加载本地的补钉,再经过跳转进入核心修复功能类SystemClassLoaderAdder.class中

这里写图片描述
* 代码中可以看出,根据Android版本的不同,分别采取具体的修复操作,不过原理都是1样的。我们以V19为例

这里写图片描述
* 从代码中可以看到,通过反射操作得到PathClassLoader的DexPatchList,反射调用patchlist的makeDexElements()方法吧本地的dex文件直代替换到Element[]数组中去,到达修复的目的。
对如何进行patch.dex与classes.dex的合并操作,这里微信开启了1个新的进程,开启新进程的服务TinkerPatchService进行合并

这里写图片描述

这里写图片描述

优势
1. 合成整包,不用在构造函数插入代码,避免verify,verify和opt在编译期间就已完成,不会在运行期间进行
2. 性能提高。兼容性和稳定性比较高。
3. 开发者透明,不需要对包进行额外处理

不足
1. 与超级补钉技术1样,不支持即时生效,必须通太重启利用的方式才能生效。
2. 需要给利用开启新的进程才能进行合并,并且很容易由于内存消耗等缘由合并失败。
3. 占用额外磁盘空间,对多DEX的利用来讲,如果修改了多个DEX文件,就需要下发多个patch.dex与对应的classes.dex进行合并操作时这类情况会更严重,因此合并进程的失败率也会更高。

阿里百川HotFix

阿里百川推出的热修复HotFix服务,相对QQ空间超级补钉技术和微信Tinker来讲,定位于紧急bug修复的场景下,能够最及时的修复bug,下拉补钉立即生效无需等待

  1. AndFix实现原理
    AndFix不同于QQ空间超级补钉技术和微信Tinker通过增加或替换全部DEX的方案,提供了1种运行时在Native修改Filed指针的方式,实现方法的替换,到达即时生效无需重启,对利用无性能消耗的目的
    这里写图片描述
  2. AndFix实现进程
    这里写图片描述
    • 以Dalvik装备为例,来分析具体的实现进程
    • setup()
      这里写图片描述
      对Dalvik来讲,遵守JIT即时编译机制,需要在运行时装载libdvm.so动态库,获得以下内部函数:
      1) dvmThreadSelf( ):查询当前的线程;
      2)dvmDecodeIndirectRef():根据当前线程取得ClassObject对象。
    • setFieldFlag
      这里写图片描述
      该操作的目的:让private、protected的方法和字段可被动态库看见并辨认。缘由在于动态库会疏忽非public属性的字段和方法
    • replaceMethod
      这里写图片描述

该步骤是方法替换的核心,替换的流程以下
这里写图片描述

优势
1. BUG修复的即时性
2. 补钉包一样采取差量技术,生成的PATCH体积小
3. 对利用无侵入,几近无性能消耗

不足
1. 不支持新增字段,和修改方法,也不支持对资源的替换。
2. 由于厂商的自定义ROM,对少数机型暂不支持

综合分析各个方案

这里写图片描述

热修复的坑和解

我们可以看到,QQ空间超级补钉技术和微信Tinker的修复原理都基于类加载,在功能上已支持类、资源的替换和新增,功能非常强大。既然已有了这么强大的热修复技术,为何阿里百川还要推出自己的热修复方案HotFix呢?

1、多DEX带来的性能问题和影响

我们知道,多DEX方案用来解决利用方法数65k的问题,现在Google也官方支持了MultiDex的实现方案。但是,这实在是利用因方法数超越而作出的不得已的下策,但是超级补钉技术和Tinker作为1种热修复的方案,平生给利用增加了多个DEX,而多DEX技术最大的问题在于性能上的坑,因此基于这类方案的补钉技术影响利用的性能是无疑的

1. 启动加载时间太长

我们可以看到,超级补钉技术和Tinker都选择在Application的attachBaseContext()进行补钉dex的加载,即便这是加载dex的最好时机,但是仍然会带来很大的性能问题,首当其冲的就是启动时间太长。
对补钉DEX来讲,利用启动时虚拟机会进行dexopt操作,将patch.dex文件转换成odex文件,这个进程非常耗时。而这个进程,又要求需要在主线程中,以同步的方式履行,否则没法成功进行修复。就DEX的加载时间,大概做了以下的时间测试
这里写图片描述
随着patch.dex的增加,在不做任何优化的情况下,启动时间也直线增长。对1个利用来讲,这简直是灾害性的

2. 易造成利用的ANR和Crash

正是特别多DEX加载致使了启动时间太长,很容易就会引发利用的ANR。我们知道当利用在主线程等待超过5s以后,就会直接致使长时间无响应而退出。超级补钉技术为保证ART不出现地址错乱问题,需要将所有关联的类全部加入到补钉中,而微信Tinker采取1种差量包合并加载的方式,都会使要加载的dex体积变得很大。这也很大程度上容易致使ANR情况的出现。
除利用ANR之外,多DEX模式也一样很容易致使Crash情况的出现。我们知道,超级补钉技术为了保证ART装备下不出现地址错乱问题,需要把修改类的所有相干类全部加入到补钉中,这里会出现1个问题,为了保证补钉包的体积最小,能否保证引入全部的关联类而不引入无关的类呢?1旦没有引入关联的类,就会出现以下的异常:
1. NoClassDefFoundError
2. Could not find class
3. Could not find method
出现这些异常,就会直接致使利用的Crash退出。
所以,不难看出如果我们需要修复1个不是Crash的BUG,但是由于未加入相干类而致使了更严重的Crash,就更加的得不偿失。
总的来讲,热修复本质的目的是为了保证利用更加稳定,而不是为了更强大的功能引入更大的风险和不稳定性。

2、热修复 or 插件化?

  • 插件化:1个程序划分为不同的部份,以插件的情势加载到利用中去,本质上它使用的技术还是热修复技术,只是加入了更多工程实践,让它支持大范围的代码更新和资源和SO包的更新。

  • 热修复:当线上利用出现紧急BUG,为了不重新发版,并且保证修复的及时性而进行的1项在线推送补钉的修复方案。

从概念上我们可以看到,插件化使用处景更多是功能,热修复使用常见在于修复。从这个层面来讲,插件化必定功能更加强大,能做的事情也更多。QQ空间超级补钉技术和微信Tinker从类、资源的替换和更新上来看,与其说是热修复,不如说是插件化。
固然,强大的功能也就增加了不稳定的因素。比如上文提到的增加启动时间,致使ANR、Crash的问题。
QQ空间超级补钉技术和微信Tinker提供了更加强大的功能,但是对利用的性能和稳定有较大的影响,就BUG修复的这个使用处景上还不够明确,并且显得太重

针对利用的性能消耗,我们可以举例做1个对照

  • 某APP的启动载入时间为3s左右,本身就是基于多DEX模式的实现。
  • 分别接入3种热修复服务,根据腾讯提供超级补钉技术和Tinker的数据,那末会变成以下的场景:
  • 阿里百川HotFix:启动时间几近无增加,不增加运行期额外的磁盘消耗。
  • QQ空间超级补钉技术:如果利用有700个类,启动耗时增加超过2.5s,到达5.5s以上。
  • 微信Tinker:假定利用有5个DEX文件,分别修改了这5个DEX,产生5个patch.dex文件,就要进行5次的patch合并动作,假定每一个补钉1M,那末就要多占用7.5M的磁盘空间。

明显对修复紧急BUG这个场景,阿里百川HotFix的更加适合,它更加轻量,可以在不重启的情况下生效,且对性能几近没有影响。微信Tinker、QQ空间超级补钉技术更多地把场景定位在发布小的新功能上,采取ClassLoader的模式,牺牲较高的性能代价去实现类、资源新增或替换的功能。阿里百川HotFix对利用本身做到无侵入,无性能消耗

总结

QQ空间超级补钉技术和微信Tinker 支持新增类和资源的替换,在1些功能化的更新上更加强大,但对利用的性能和稳定会有的1定的影响;阿里百川HotFix虽然暂时不支持新增类和资源的替换,对新功能的发布也有所限制,但是作为1项定位为线上紧急BUG的热修复的服务来讲,能够真正做到BUG即时修复用户无感知,同时保证对利用性能不产生没必要要的消耗,在热修复方面不失为1个好的选择!

------分隔线----------------------------
------分隔线----------------------------

最新技术推荐