| Tinker | 15.2k | 29 days ago | 1.9.14.7 |
| Robust | 3.7k | 4 months ago | 0.4.99 |
| Aceso | 791 | 3 years ago | 0.0.3 |
| Amigo | 1.3k | 3 years ago | 0.6.* |
| RicooFix | 1.6k | 4 years ago | 无 |
可以看到,近期还在更新的有Tinker和Robust,其他的都是至少三年之前的更新。
如何选择热修复框架
三个方面进行考虑
1.项目需求
方法级别修复,资源修复,so库的修复
对平台兼容性要求和成功率要求
有需求对分发进行控制,对监控数据进行统计,补丁包进行管理
是否付费
2.学习,使用成本
学习成本
代码侵入性
调试维护成本
3.技术保障,稳定性
比如GitHub Star,大公司技术保障,专人维护
热度高,社区活跃
小结
从这三个方面考虑,最后筛选出三个比较优秀的热修复库,Sophix,Tinker,Robust
如果考虑付费,Sophix和Tinker付费版(云服务),我支持Sophix,性能消耗低,支持即时生效,对代码无侵入,免费阈值的支持更好。
如果不考虑付费,只需要支持方法级别的Bug修复,不支持资源以及so库,推荐使用Robust,否则使用Tinker免费版。
当然如果公司实力够牛逼,可以考虑自研,灵活性以及可控性最强。
代码、资源、so库修复
AndroidManifest出现Bug是无法修复的,因为它是由系统进行解析的,系统会直接获取安装包里唯一的AndroidMainfest.xml文件,在解析过程不会访问补丁包信息。
代码修复:任何的热修复方案,想要改变代码逻辑,都需要在补丁包里包含一个新逻辑的dex文件。
资源修复:有些资源,比如桌面图标,通知栏图标以及RemoteView之类的资源,是由系统直接解析安装包里的资源得到的,因此对于这类资源,任何热修复方案都无法进行资源替换和修复。
so库修复:so库的修复思路应该是最明确的。在Android系统中,所有的so库都是由System.load进行加载的,因此只要找到办法在加载的时候优先加载补丁包的so库,而不是加载原有安装包的so库,就能够进行完整的底层代码替换了。
代码修复
代码修复主要有三大主要方案,阿里系的底层替换和腾讯系的类加载方案以及美团的javaHook方案(Instant Run原理)。
- 底层替换方案限制颇多,但是时效性最好,加载轻快,立即见效。
传统的底层替换方案(Dexposed,AndFix),依赖直接修改虚拟机方法实体的具体字段实现的。不同厂商/版本对ArtMethod结构体的结构修改带来的问题。每个Java方法在art中都对应着一个ArtMethod,ArtMethod记录了这个Java方法的所有信息,包括所属类,访问权限,代码执行地址等等。
实现一种不修改底层具体结构的替换方式,解决兼容性问题,代码量大大减少。native层面替换,把ArtMethod方法作为整体进行替换。
- 类加载方案时效性差,需要重新类启动才能见效,但修复范围广,限制少。
类加载方案的原理是在App重新启动后让Classloader去加载新的类。
QQ空间方案会入侵打包流程,并且为hack添加一些无用的信息,不优雅。
QFix方案需要获取底层虚拟机的函数,稳定性不够,无法新增public函数
Tinker方案是完整的全量的dex文件加载,将补丁合成的方案做到了极致,从dex的方法和指令维度进行全量合成,对于dex内容的比较粒度过细,实现较为复杂,性能消耗比较严重,时空转换性价比不高。
dex比较的最佳粒度,应该是类的维度,采用全量合成dex的技术,这个技术方案是从手机淘宝插件化框架Atlas汲取的。直接利用Android原有的类查找与合成机制,快速合成新的的全量dex文件。这样既不需要处理合成时的方法数超过原有方法数的情况,也不会对dex的结构进行破坏性重构。
我们重新编排了包中dex文件的顺序。这样虚拟机在查找类的时候,会优先找到classes.dex中的类,然后才是classes2.dex,classes3.dex,也可以看作是dex文件级别的类插桩方案。这个方式十分巧妙,它对旧包与补丁包中的classes.dex顺序进行了打破和重组,最终使系统可以自然的识别到这个顺序,以实现类覆盖的目的,大大减少合成补丁的开销。
Sophix使用了两者的结合,自动选择,小修改,在底层替换方案限制范围内的,直接采用底层替换热修复,可以做到及时生效。其他采用类加载替换方案。
- Robust的JavaHook方案的原理与Instant Run的代码插桩原理一致,优点是实时生效,不需要重新启动,高兼容性(Robust只是在正常的使用DexClassLoader),高稳定性,修复成功率高达99%。支持方法级别的修复,包括静态方法。支持增加方法和类。支持ProGuard的混淆,内联优化等操作。
缺点是代码是侵入式的,会在原有的类中加入相关代码,so库和资源的替换暂时不支持。会增大apk的体积,平均一个函数会比原来增加17.47个字节,10万个函数会增加1.67M。
Sophix方案
热部署(及时生效)
在native层面替换,把ArtMethod方法作为整体进行替换,实时生效,但同时也会带来诸多限制:
访问权限的问题
-
方法调用时的权限检查
-
同名包下的权限问题(设置新类的ClassLoader为原来类就可以了,通过反射进行设置)
-
反射调用非静态方法产生的问题
及时生效带来的限制
以下两种情况是不适用的:
-
引起了原有的类中发生结构变化的修改(字段,方法增加减少),改成以冷启动支持
-
修复了的非静态方法会被反射调用,改成以冷启动方式支持
编译期和语言特性的影响
1. 内部类编译
-
静态内部类/非静态内部类的区别
-
内部类和外部类互相访问
外/内部类为了访问内/外部类私有的域/方法,编译器会自动为内部类生成access$数字编号相关方法,JVM规范,好像是提供了一个静态方法
- 热部署解决方案
一个外部类如果有内部类,把所有的method/field的私有访问权限改成protected或public或者默认访问权限。
同时把内部类所有的method/field的私有访问权限改成protected或public或者默认访问权限。
2. 匿名内部类编译
-
匿名内部类编译命名规则(access$**)
-
热部署解决方案
应该极力避免插入一个新的匿名内部类。当然如果匿名内部类是插入到外部类的末尾