Tinker原理深入理解(二)
来源:互联网 发布:mac卸载 编辑:程序博客网 时间:2024/05/18 01:54
原理及介绍
现阶段,Android热补丁技术应该分为以下两个流派:
- Native:代表有阿里的Dexposed、AndFix与腾讯的内部方案KKFix;
- Java:代表有Qzone的超级补丁、大众点评的nuwa、美团的robust、百度金融的rocooFix, 饿了么的amigo。
Native流派与Java流派都有着自己的优缺点,它们具体差异可参考此文:微信Android热补丁实践演进之路 。微信Tinker属于Java流派。核心思想是利用DexDiff算法对比差异生成Patch补丁包,分平台合成,全量替换新的Dex。
校验Dex
Tinker它合并完成时完全使用了新的Dex,并且实现了分平台合成。这样既不出现Art地址错乱的问题,在Dalvik环境也无须插桩。当然考虑到补丁包的体积,我们不能直接将新的Dex放在里面。但可以将新旧两个Dex的差异放到补丁包中,这里主要调研的方法有以下几个:
1.BsDiff;它格式无关,但对Dex效果不是特别好,而且非常不稳定。当前微信对于so与部分资源,依然使用bsdiff算法;
2.DexMerge;它主要问题在于合成时内存占用过大,一个12M的dex,峰值内存可能达到70多M;
3.DexDiff;通过深入Dex格式,实现一套diff差异小,内存占用少以及支持增删改的算法。
DexDiff算法已经非常复杂,事实上要实现分平台合成并不容易。主要难点有以下几个方面:
- small dex的类收集;什么类应该放在这个小的Dex中呢?
- ClassN处理;对于ClassN怎么样处理,可能出现类从一个Dex移动到另外一个Dex?
- 偏移二次修正; 补丁包中的操作序列如何二次修正?
- Art.info的大小; 如何修正偏移所引入的info文件的大小?
微信团队最后实现了这一套方案,这也是其他全量合成方案所不能做到的:
- Dalvik全量合成,解决了插桩带来的性能损耗;
- Art平台合成small dex,解决了全量合成方案占用Rom体积大, OTA升级以及Android N的问题;
- 大部分情况下Art.info仅仅1-20K, 解决由于补丁包可能过大的问题;
事实上,DexDiff算法变的如此复杂,怎么样保证它的正确性呢?微信为此做了以下三件事情:
- 随机组成Dex校验,覆盖大部分case;
- 微信200个版本的随机Diff校验, 覆盖日常使用情况;
- Dex文件合成产物有效性校验,即使算法出现问题,也只是编译不出补丁包。
每一次DexDiff算法的更新,都需要经过以上三个Test才可以提交,这样DexDiff的这套算法已完成了整个闭环。
DexDiff 算法
它属于二路归并算法,对Dexdiff算法有兴趣研究的童鞋可以看看这里:Tinker Dexdiff算法解析
算法过程描述:
1.首先我们需要将新旧内容排序,这需要针对排序的数组进行操作。
2.新旧两个指针,在内容一样的时候 old、new 指针同时加1,在 old 内容小于 new 内容(注:这里所说的内容比较是单纯的内容比较比如’A’<’a’)的时候 old 指针加1 标记当前 old 项为删除。
3.在 old 内容大于 new 内容 new 指针加1, 标记当前 new 项为新增。
下面是算法执行的简单过程:
------old-----11 foo2 12 foo5 13 hello dodola14 hello dodola115 hello dodola216 hello dodola517 out18 println------new-----11 foo3 12 foo5 13 hello dodola1 14 hello dodola315 hello dodola_modify16 out17 println对比的old cursor 和 new cursor 指针的改变以及操作判定,判定过程如下old_11 new_11 cmp <0 delold_12 new_11 cmp >0 addold_12 new_12 cmp =0 noold_13 new_13 cmp <0 delold_14 new_13 cmp =0 noold_15 new_14 cmp <0 delold_16 new_14 cmp >0 addold_16 new_15 cmp <0 delold_17 new_15 cmp >0 addold_17 new_16 cmp =0 noold_18 new_17 cmp =0 nobreak;进入下一步过程可以确定的是删除的内容肯定是从 old 中的 index 进行删除的 添加的内容肯定是从 new 中的 index 中来的,按照这个逻辑我们可以整理如下内容。old_11 delnew_11 addold_13 delnew_14 addold_15 delnew_15 addold_16 del到这一步我们需要找出替换的内容,很明显替换的内容就是从 old 中 del 的并且在 new 中 add 的并且 index 相同的i tem,所以这就简单了old_11 replaceold_13 delnew_14 addold_15 replaceold_16 delok,到这一步我们就能判定出两个dex的变化了,很机智的算法。
运行时替换PathClassLoader
事实上,App image中的class是插入到PathClassloader中的ClassTable中。假设我们完全废弃掉PathClassloader,而采用一个新建Classloader来加载后续的所有类,即可达到将cache无用化的效果。
需要注意的问题是我们的Application类是一定会通过PathClassloader加载的,所以我们需要将Application类与我们的逻辑解耦,这里方式有两种:
1.采用类似instant run的实现;在代理application中,反射替换真正的application。这种方式的优点在于接入容易,但是这种方式无法保证兼容性,特别在反射失败的情况,是无法回退的。
2.采用代理Application实现的方法;即Application的所有实现都会被代理到其他类,Application类不会再被使用到。这种方式没有兼容性的问题,但是会带来一定的接入成本。
其他的一些问题:
由于原理与系统限制,Tinker有以下已知问题:
Xposed等微信插件; 市面上有各种各样的微信插件,它们在微信启动前会提前加载微信中的类,这会导致两个问题:
- Dalvik平台:出现Class ref in pre-verified class resolved to unexpected implementation的crash;
- Art平台:出现部分类使用了旧的代码,这可能导致补丁无效,或者地址错乱的问题。
微信在这里的处理方式是:若crash时发现安装了Xposed,即清除并不再应用补丁。
Dex反射成功但是不生效;
部分三星android-19版本存在Dex反射成功,但出现类重复时,查找顺序始终从base.apk开始。 微信在这里的处理方式是增加Dex反射成功校验,具体通过在框架中埋入某个类的isPatch变量为false。在补丁时,我们自动将这个变量改为true。通过这个变量最终的数值,则可以知道反射成功与否。不支持部分三星android-21机型,加载补丁时会主动抛出”TinkerRuntimeException:checkDexInstall failed”;
T不支持修改AndroidManifest.xml,Tinker不支持新增四大组件;
由于Google Play的开发者条款限制,不建议在GP渠道动态更新代码;
在Android N上,补丁对应用启动时间有轻微的影响;
对于资源替换,不支持修改remoteView。例如transition动画,notification icon以及桌面图标。
2017-8-4 更新 :
1.7.11版本或以下版本,项目中若使用了RxJava,在调用特殊操作符时会报如下错误:
java.lang.VerifyError: Rejecting class foc because it failed compile-time verification (declaration of 'foc' appears in /data/user/0/com.xxx.android/tinker/patch-91b25513/dex/classes5.dex.jar)at euy.zipArray(SourceFile:4567) at euy.zip(SourceFile:3883) at euy.zipWith(SourceFile:13590) at aqi.c(SourceFile:67)...
具体原因:RxJava里的zip特殊操作符逻辑加上art对aput这个指令的校验方式凑在一起,导致Crash 。Tinker 官方GitHub 项目 Issue 链接地址:https://github.com/Tencent/tinker/issues/491
参考文献:
微信移动技术团队GitHub博客地址: WeMobileDev/article
Tinker GitHub Wiki :tinker/wiki
Tinker Dexdiff算法解析 : Dexdiff算法解析
Android_N混合编译与对热补丁影响:WeMobileDev/article/Android_N
- Tinker原理深入理解(二)
- 深入理解ButterKnife源码并掌握原理(二)
- 深入理解操作系统原理之进程管理(二)
- 深入理解overlayfs(二):使用与原理分析
- [堆排序之二]深入理解原理
- 深入理解mybatis原理(二) 关联查询
- 深入理解REST(二)
- 深入理解缓冲区(二)
- 《深入理解计算机系统》(二)
- GCD 深入理解(二)
- GCD 深入理解(二)
- GCD 深入理解(二)
- GCD 深入理解(二)
- GCD 深入理解(二)
- GCD 深入理解(二)
- GCD深入理解(二)
- GCD 深入理解(二)
- GCD 深入理解(二)
- 编程模拟飞船加速变轨过程-物理基础篇(3)Kepler轨道及其描述(上)
- bat 脚本之 使用函数
- Using Predefined CSS Margin Classes
- spring原理
- Linux环境下C++单元测试Gtest 入门
- Tinker原理深入理解(二)
- BZOJ 4525: [Usaco2016 Jan]Angry Cows 二分
- NodeJS -- 异步I/O
- Word2003快速操作技巧及常用快捷键使用
- PowerPC家族谱系详解
- 训练自己的数据,微调faster Rcnn模型
- C++ 实现USB
- laravel:服务提供者的实际应用
- HDP 2.4 离线安装