ART环境对Android热修复方案的影响分析

一、ART(Android Runtime)

ART是Android在4.4版本中引入的新虚拟机环境,在5.0版本正式取代了Dalvik VM。ART环境下,App安装时其包含的Dex文件将被dex2oat预编译成目标平台的机器码,从而提高了App的运行效率。在这个预编译过程中,dex2oat对目标代码的优化过程与Dalvik VM下的dexopt有较大区别,尤其是在5.0版本以后ART环境下新增的方法内联优化。由于方法内联改变了原本的方法分布和调用流程,对热修复方案势必会带来影响。

浏览Android源码可知,Android用来生成oat文件的Compiler有多种实现,各Android版本中存在的实现类型和默认使用的类型如下:

Android版本 Compiler类型 默认使用 备注
4.4 kQuick kQuick 此版kQuick未引入方法内联优化
5.x

kQuick

kOptimizing

kPortable

kQuick kPortable是半成品,未实际使用
6.x

kQuick

kOptimizing

kQuick 从此版本开始,kOptimizing加入下文归纳的新内联特性
7.0 kOptimizing kOptimizing
7.1 kOptimizing kOptimizing  

Optimizing Compiler的内联条件可以从art/compiler/optimizing/inliner.cc里的HInliner::Run()方法开始分析,篇幅关系这里同样直接给出结论。对于Optimizing Compiler,当以下条件均满足时被调用的方法将被inline:

  1. App不是Debug版本的;
  2. 被调用的方法所在的类与调用者所在的类位于同一个Dex;(注意,符合Class N命名规则的多个Dex要看成同一个Dex)
  3. 被调用的方法的字节码条数不超过dex2oat通过--inline-max-code-units指定的值,6.x默认为100,7.x默认为32;
  4. 被调用的方法不含try块;
  5. 被调用的方法不含非法字节码;
  6. 对于7.x版本,被调用方法还不能包含对接口方法的调用。(invoke-interface指令)

此外Optimizing Compiler方法内联可以跨多级方法调用进行,若有这样的调用链:method1->method2->method3->method4,则在四个方法都满足内联条件的情况下,最终内联的结果将是method1包含method2,method3,method4的代码,method2包含method3,method4的代码,以此类推。但这种跨调用链内联会受到调用dex2oat时通过--inline-depth-limit参数指定的值的限制,默认为5,即超过5层的调用就不会再被内联到当前方法了。

二、主流热修复方案

目前主流热修复方案可分为Native派和Java派。

Native派的做法大致有以下两种:

(1)用新方法的Native描述结构体覆盖旧方法的Native描述结构体,从而替换旧方法的逻辑

(2)将旧方法修改为Native类型,并将其实现指向一个公共分发函数,由该函数负责调用新方法

Java派的做法也有两种:

(1)将修改过的类汇集成一个Dex,插入到BaseDexClassLoader的DexPathList的最前面,这样在加载类时就会优先加载修改过的类

(2)对每个方法插一段逻辑,此逻辑判断方法是否被打补丁,是则执行新逻辑

三、ART内联对热修复方案的影响

(1)对Native派而言,如果修改的方法被内联到了调用者的代码里,则修改将不会生效,因为内联的代码不再需要方法调用,也就不会涉及到额外的Native层方法描述结构体。

(2)对于Java派而言,插入一段逻辑的做法基本不受影响,因为内联时会将被修改的方法连同插入的那段逻辑一起复制到调用者的代码里,结果和内联之前是等价的。但通过优先加载补丁Dex里的类来取代旧类的做法就会受到影响。

    方法内联之所以会导致优先加载补丁Dex的方案出现上述问题,本质上是因为补丁Dex只覆盖了旧Dex里的一部分类,一旦被覆盖的类的方法被内联到了调用者里,则加载类的过程还是正常的,即从补丁Dex里加载了新版本的类。但由于内联,执行流程并未跳转到新的方法里,于是所有关于新版本的类的方法、成员、字符串的查找用的就都是旧方法里的索引了。这里“用旧索引找新目标”app自然就可能出现异常。

四、可能的应对方案

(1)根据内联条件,如果阻止方法内联,就可以避免出现机器码里用旧typeid去新DexCache里查找类的情况了。对我们来说比较方便的条件就是在每个方法前面插入一个空try块,这样这些方法就不会参与内联了。不过考虑到ART的内联触发条件随时都在更新,这样做也不能一劳永逸,如何内联的条件改变,以后就需要持续跟进。

(2)另外一个思路是把修改类的整个调用链(调用修改类的类,与调用[调用修改类的类]的类,一直递归下去)都放到补丁中,需要包括所有可能被影响的类。这套规则的主要问题在于整个完整调用链的类会非常庞大,很有可能与全量差别不大,其次不排除Android未来有新的优化导致这样的方式会失效。

(3)最保守的方案是去掉ART环境下的合成增量Dex的逻辑,直接合成全量的NewDex,这样除了loader类,所有方法统一都用了NewDex里的,也就不怕有方法被内联了。至于全量新Dex在系统OTA之后触发dex2oat可能导致App启动时ANR的问题,可以通过在进入ApplicationLike之前判断fingerprint是否变化来得知系统是否进行过OTA,然后根据判断结果手动触发多线程dex2oat加以缓解的。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值