android里热修复框架有很多,比较知名的有
Dexposed(阿里 停止维护),Andfix(阿里 停止维护),Robust(美团点评),Amigo(停止维护),Nuwa(QQ空间团队 停止维护),Tinker(腾讯),Sophix(手淘+阿里云团队维护)...
热修复大致有两种解决方案
- 底层替换方案 限制颇多,但时效性最好,加载轻快,立即见效。
Dexposed,Andfix - 类加载方案 时效性差,需要重新冷启动才能见效,但修复范围广,限制少。
Nuwa Tinker
底层替换方案
底层替换方案是在已经加载了的类中直接替换掉原有方法,是在原来类的基础上进行修改的。因而无法实现对与原有类进行方法和字段的增减,因为这样将破坏原有类的结构。
一旦补丁类中出现了方法的增加和减少,就会导致这个类以及整个Dex的方法数的变化。方法数的变化伴随着方法索引的变化,这样在访问方法时就无法正常地索引到正确的方法了。如果字段发生了增加和减少,和方法变化的情况一样,所有字段的索引都会发生变化。并且更严重的问题是,如果在程序运行中间某个类突然增加了一个字段,那么对于原先已经产生的这个类的实例,它们还是原来的结构,这是无法改变的。而新方法使用到这些老的实例对象时,访问新增字段就会产生不可预期的结果。
类加载方案
java->class->dex->odex
类加载方案的原理是在app重新启动后让Classloader去加载新的类。因为在app运行到一半的时候,所有需要发生变更的类已经被加载过了,在Android上是无法对一个类进行卸载的。如果不重启,原来的类还在虚拟机中,就无法加载新类。因此,只有在下次重启的时候,在还没走到业务逻辑之前抢先加载补丁中的新类,这样后续访问这个类时,就会Resolve为新类。从而达到热修复的目的。 DexClassLoader.java
各框架的基本原理及优缺点
-
阿里Dexposed -- native解决方案
原理
-
直接在native层进行方法的结构体信息对换,从而实现完美的方法新旧替换,从而实现热修复功能 他的思想完全来源于Xposed框架,完美诠释了AOP编程,这里用到最核心的知识点就是在native层获取到指定方法的结构体,然后改变他的nativeFunc字段值,而这个值就是可以指定这个方法对应的native函数指针,所以先从Java层跳到native层,改变指定方法的nativeFunc值,然后在改变之后的函数中调用Java层的回调即可。实现了方法的拦截功能。
-
基于开源框架Xposed实现,是一种AOP解决方案
-
只Hook App本身的进程,不需要Root权限
优点:
即时生效 不需要任何编译器的插桩或者代码改写,对正常运行不引入任何性能开销。这是AspectJ之类的框架没法比拟的优势; 对所改写方法的性能开销也极低(微秒级),基本可以忽略不计; 从工程的角度来看,热补丁仅仅是牛刀小试,它真正的威力在于『线上调试』; 基于Xposed原理实现的AOP不仅可以hook自己的代码,还可以hook同进程的Android SDK代码,这也就可以让我们有能力在App中填上Google自己挖的坑。
缺点:
Dalvik上近乎完美,不支持ART(需要另外的实现方式),所以5.0以上不能用了; 最大挑战在于稳定性与兼容性,而且native异常排查难度更高; 由于无法增加变量与类等限制,无法做到功能发布级别;
-
-
阿里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等
-
Nuwa--Dex分包方案
原理:
原理是Hook了ClassLoader.pathList.dexElements[]。因为ClassLoader的findClass是通过遍历dexElements[]中的dex来寻找类的。当然为了支持4.x的机型,需要打包的时候进行插桩。 越靠前的Dex优先被系统使用,基于类级别的修复
优点:
不需要考虑对dalvik虚拟机和art虚拟机做适配 代码是非侵入式的,对apk体积影响不大
缺点:
需要下次启动才会生效 最大挑战在于性能,即Dalvik平台存在插桩导致的性能损耗,Art平台由于地址偏移问题导致补丁包可能过大的问题
-
美团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内联的函数不能被内联了
-
微信Tinker
原理:
服务端做dex差量,将差量包下发到客户端,在ART模式的机型上本地跟原apk中的classes.dex做merge,merge成为一个新的merge.dex后将merge.dex插入pathClassLoader的dexElement,原理类同Q-Zone,为了实现差量包的最小化,Tinker自研了DexDiff/DexMerge算法。Tinker还支持资源和So包的更新,原理类同InstantRun
优点:- 支持动态下发代码
- 支持替换So库以及资源
缺点:
- 不能即时生效,需要下次启动
微信已知问题:
- Tinker不支持修改AndroidManifest.xml,Tinker不支持新增四大组件(1.9.0支持新增非export的Activity);
- 由于Google Play的开发者条款限制,不建议在GP渠道动态更新代码;
- 在Android N上,补丁对应用启动时间有轻微的影响;
- 不支持部分三星android-21机型,加载补丁时会主动抛出"TinkerRuntimeException:checkDexInstall failed";
- 对于资源替换,不支持修改transition动画,notification icon以及桌面图标。
横向对比
| Tinker | Nuwa | Andfix | Robust | Sophix | |
|---|---|---|---|---|---|
| 类替换 | yes | yes | no | no | yes |
| so包替换 | yes | no | no | no | yes |
| 资源替换 | yes | yes | no | no | yes |
| 全平台支持 | yes | yes | no | no | yes |
| 即时生效 | no | no | yes | yes | yes |
| 新增四大组件 | 1.9.0版本之后支持新增exported为false的activity | no | no | no | no |
| 性能损耗 | 较大 | 较大 | 一般 | 一般 | 较小 |
| 补丁包大小 | 较小 | 较大 | 一般 | 一般 | 较小 |
| 复杂度 | 一般 | 一般 | 复杂 | 复杂 | 较低 |
| Rom体积 | Dalvk较大 | 较小 | 较小 | 较小 | 较小 |
| 成功率 | 较高 | 较高 | 一般 | 高 | 高 |
Sophix和Tinker无疑是最好的两个热修复框架