很早之前就想深入的研究和学习一下热修复,由于时间的原因一直拖着,现在才执笔弄起来。
Android而更新系列:
Android热更新一:JAVA的类加载机制
Android热更新二:理解Java反射
Android热更新三:Android类加载机制
Android热更新四:热修复机制
Android热更新五:四大热修复方案分析
Android热更新六:Qzone热更新原理
Android热更新七:Tinker热更新原理
Android热更新八:AndFix热更新原理
Android热更新九:Robust热更新原理
Android热更新十:自己写一个Android热修复
超级补丁技术基于DEX分包方案,使用了多DEX加载的原理,大致的过程就是:把BUG方法修复以后,放到一个单独的DEX里,插入到dexElements数组的最前面,让虚拟机去加载修复完后的方法。
当patch.dex中包含Test.class时就会优先加载,在后续的DEX中遇到Test.class的话就会直接返回而不去加载,这样就达到了修复的目的。
但是有一个问题是,当两个调用关系的类不在同一个DEX时,就会产生异常报错。我们知道,在APK安装时,虚拟机需要将classes.dex优化成odex文件,然后才会执行。在这个过程中,会进行类的verify操作,如果调用关系的类都在同一个DEX中的话就会被打上CLASS_ISPREVERIFIED
的标志,然后才会写入odex文件。
所以,为了可以正常地进行打补丁修复,必须避免类被打上CLASS_ISPREVERIFIED
标志,具体的做法就是单独放一个类在另外DEX中,让其他类调用。
我们来逆向手机QQ空间APK看一下具体的实现:
先进入程序入口QZoneRealApplication
,在attachBaseContext
中进行了两步操作:修复CLASS_ISPREVERIFIED
标志导致的unexpected DEX problem异常、加载修复的DEX。
1. 修复Unexpected DEX Problem异常
先看代码,
可以看到,这里是要加载一个libs目录下的dalvikhack.jar。在项目的assets/libs找到该文件,解压得到’classes.dex’文件,逆向打开该DEX文件,
通过不同的DEX加载进来,然后在每一个类的构造方法中引用其他DEX中的唯一类AnitLazyLoad,避免类被打上CLASS_ISPREVERIFIED标志。
在无修复的情况下,将DO_VERIFY_CLASSES设置为false,以提高性能。只有在需要修复的时候,才设置为true。
至于如何加载进来,与下面第二个步骤基本相同。
2. 加载修复的DEX
从loadPatchDex()方法进入,经过几次跳转,到达核心的代码段,SystemClassLoaderInjector.c()
。由于进行了混淆和多次方法的跳转,于是将核心代码段做了如下整理:
修复的步骤为:
- 可以看出是通过获取到当前应用的Classloader,即为BaseDexClassloader
- 通过反射获取到他的DexPathList属性对象pathList
- 通过反射调用pathList的dexElements方法把patch.dex转化为Element[]
- 两个Element[]进行合并,把patch.dex放到最前面去
- 加载Element[],达到修复目的
整体的流程图如下:
从流程图来看,可以很明显的找到这种方式的特点:
优势:
没有合成整包(和微信Tinker比起来),产物比较小,比较灵活
可以实现类替换,兼容性高。(某些三星手机不起作用)
不足:
- 不支持即时生效,必须通过重启才能生效。
- 为了实现修复这个过程,必须在应用中加入两个dex!dalvikhack.dex中只有一个类,对性能影响不大,但是对于patch.dex来说,修复的类到了一定数量,就需要花不少的时间加载。对手淘这种航母级应用来说,启动耗时增加2s以上是不能够接受的事。
- 在ART模式下,如果类修改了结构,就会出现内存错乱的问题。为了解决这个问题,就必须把所有相关的调用类、父类子类等等全部加载到patch.dex中,导致补丁包异常的大,进一步增加应用启动加载的时候,耗时更加严重。
网友评论