Tinker原理深入理解(二)

原理及介绍

现阶段,Android热补丁技术大致分为以下三个流派:

  • Native方案:代表有阿里的 Dexposed、AndFix 与腾讯的内部方案 KKFix;
  • Java方案:代表有 Qzone 的超级补丁、微信的 Tinker、大众点评的 Nuwa、美团的 Robust、百度金融的 RocooFix、饿了么的 Amigo。
  • 混合方案 阿里的 Sophix。

Native流派与Java流派都有着自己的优缺点,它们具体差异可参考此文:微信Android热补丁实践演进之路 。微信Tinker属于Java流派。核心思想是利用DexDiff算法对比差异生成Patch补丁包,全量替换新的Dex。(原来分平台合成的方案,由于art下的内联策略问题,已经废弃了,改成全量合成)

微信Tinker原理图 

Tinker流程图

校验Dex

Tinker它合并完成时完全使用了新的Dex,并且实现了分平台合成。这样既不出现Art地址错乱的问题,在Dalvik环境也无须插桩。当然考虑到补丁包的体积,我们不能直接将新的Dex放在里面。但可以将新旧两个Dex的差异放到补丁包中,这里主要调研的方法有以下几个:

Diff文件

  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 dodola
14 hello dodola1
15 hello dodola2
16 hello dodola5
17 out
18 println
------new-----
11 foo3 
12 foo5 
13 hello dodola1 
14 hello dodola3
15 hello dodola_modify
16 out
17 println
对比的old cursor 和 new cursor 指针的改变以及操作判定,判定过程如下
old_11 new_11 cmp <0  del
old_12 new_11 cmp >0  add
old_12 new_12 cmp =0  no
old_13 new_13 cmp <0  del
old_14 new_13 cmp =0  no
old_15 new_14 cmp <0  del
old_16 new_14 cmp >0  add
old_16 new_15 cmp <0  del
old_17 new_15 cmp >0  add
old_17 new_16 cmp =0  no
old_18 new_17 cmp =0  no
break;
进入下一步过程
可以确定的是删除的内容肯定是从 old 中的 index 进行删除的 添加的内容肯定是从 new 中的 index 中来的,按照这个逻辑我们可以整理如下内容。
old_11 del
new_11 add
old_13 del
new_14 add
old_15 del
new_15 add
old_16 del
到这一步我们需要找出替换的内容,很明显替换的内容就是从 old 中 del 的并且在 new 中 add 的并且 index 相同的i tem,所以这就简单了
old_11 replace
old_13 del
new_14 add
old_15 replace
old_16 del
ok,到这一步我们就能判定出两个dex的变化了,很机智的算法。

运行时替换PathClassLoader

事实上,App image中的class是插入到PathClassloader中的ClassTable中。假设我们完全废弃掉PathClassloader,而采用一个新建Classloader来加载后续的所有类,即可达到将cache无用化的效果。

需要注意的问题是我们的Application类是一定会通过PathClassloader加载的,所以我们需要将Application类与我们的逻辑解耦,这里方式有两种:

  1.采用类似instant run的实现;在代理application中,反射替换真正的application。这种方式的优点在于接入容易,但是这种方式无法保证兼容性,特别在反射失败的情况,是无法回退的。

  2.采用代理Application实现的方法;即Application的所有实现都会被代理到其他类,Application类不会再被使用到。这种方式没有兼容性的问题,但是会带来一定的接入成本。

  启动耗时对比

其他的一些问题:

由于原理与系统限制,Tinker有以下已知问题:

  • Xposed等微信插件; 市面上有各种各样的微信插件,它们在微信启动前会提前加载微信中的类,这会导致两个问题:

    1. Dalvik平台:出现Class ref in pre-verified class resolved to unexpected implementation的crash;
    2. 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
此bug 在1.8.0版本已修复。

参考文献:

微信移动技术团队GitHub博客地址: WeMobileDev/article
Tinker GitHub Wiki :tinker/wiki
Tinker Dexdiff算法解析 : Dexdiff算法解析
Android_N混合编译与对热补丁影响:WeMobileDev/article/Android_N

猜你喜欢

转载自blog.csdn.net/qq_22393017/article/details/73999210