热更新系列目录
博客创建时间:2020.05.20
博客更新时间:2021.02.23
前言
热修复技术Tinker中的核心是类加载替换,该技术是基于multidex原理的类加载方法,如对类的加载不甚了解,请阅读我的博客 《类加载机制原理解析》 进行科普。本篇博客主要是对腾讯系的Tinker热修复方案进行讲解,同时补充下腾讯系的其他热修复方案。
如不看懂
类加载
的原理,将无法真正理解Tinker的底层实现
Tinker
在Tinker之前腾讯系热修复已有Qzone等技术方案,但是存在这部分重大缺陷,因此Tinker为解决相关缺陷而问世。
Tinker针对Qzone的不足,不再将patch.dex增加到elements数组中,而是差量的方式给出patch.dex,然后将patch.dex与应用的classes.dex合并,然后整体替换掉旧的DEX文件,以达到修复的目的。
核心原理
Tinker的热修复实质就是利用Element[] dexElements的顺序来做文章。 当一个补丁的patch.dex放到了dexElements的第一位。那么当加载一个bug类时,发现在patch.dex中,则直接加载这个类,原来的bug类可能就被覆盖了。 一旦一个类被加载了,将不再重复加载同一个类,也即bug被修复。
热修复过程:
- Tinker 方案参考了multidex的实现原理,在编译时通过新旧两个APK的Dex生成差异patch.dex。
- 通过相关平台将patch.apk下发到终端,程序运行时,将差异patch.dex重新和旧的dex合并还原成新的.dex。由于合并的过程比较费时,所以有一个单独的PatchService进行合并。
- 为了减小patch的大小,Tinker自研了DexDiff算法,深度利用Dex的格式减小差异大小
- 程序再次启动后,Tinker是全量合成的新dex插入到dexElements最前面。使得新的.dex中的内容Class优先加载,bug得到修复。
注意
- 为了减小patch的大小,Tinker自研了DexDiff算法,深度利用Dex的格式减小差异大小。让patch只包含有差异的部分,相同的部分不包含
- Tinker的补丁包形式是.apk格式,除了patch.dex,还包含resource差异包等文件
- 由于合并的过程比较费时,所以有一个单独的PatchService进行合并。
- 类加载实现原理涉及了dex文件的重新解压缩合并等处理,消耗的内存大,时间长,系统内存低时容易合并失败。也即类加载热更新存在一定的失败率。
Tinker热更新流程
Tinker优缺点
优点
- 合成整包,不用在构造函数插入代码,防止verify。verify和opt在编译期间就已经完成,不会在运行期间进行
- 性能提高。兼容性和稳定性比较高。
- Tinker补丁的生成是通过Gradle插件来实现的,打包过程对开发者透明,不需要对包进行额外处理。
缺点
-
与Qzone补丁技术一样,不支持即时生效,必须通过重启应用的方式才能生效(如果是软件启动后才发布的补丁,需要重启两次)。采用了ClassLoader机制
-
需要给应用开启新的进程才能进行合并,并且很容易因为内存消耗等原因合并失败。
-
合并时占用额外磁盘空间,对于多DEX的应用来说,如果修改了多个DEX文件,就需要下发多个patch.dex与对应的classes.dex进行合并操作时这种情况会更严重,因此合并过程的失败率也会更高。
-
Tinker的接入侵入性太高了,打包也麻烦费时间。
QZone
Qzone也是采用的类加载的方案,它修复后是单独放在一个.dex中,插入到dexElements数组的最前面,让虚拟机去加载修复完后的方法。
这样的热修复有一个大问题就是CLASS_ISPREVERIFIED
当两个调用关系的类不在同一个DEX时,就会产生异常报错。在APK安装时,虚拟机需要将classes.dex优化成odex文件,然后才会执行。
在这个过程中,会进行类的verify操作,如果调用关系的类都在同一个DEX中的话就会被打上CLASS_ISPREVERIFIED的标志,然后才会写入odex文件。
修复的步骤:
- 可以看出是通过获取到当前应用的Classloader,即为BaseDexClassloader
- 通过反射获取到他的DexPathList属性对象pathList
- 通过反射调用pathList的dexElements方法把patch.dex转化为Element[]
- 两个Element[]进行合并,把patch.dex放到最前面去
- 加载Element[],达到修复目的
总结
QZone与Tinker的类加载有点区别, QZone不用patch.dex与旧的.dex合并,所以会出现CLASS_ISPREVERIFIED问题。而Tinker的patch.dex与旧的.dex合并后在同一个.dex中,其实是将patch.dex的内容顺序排在了修复.dex的前面,能优先加载.patch里面的类。
相关链接:
扩展链接:
博客书写不易,您的点赞收藏是我前进的动力,千万别忘记点赞、 收藏 ^ _ ^ !