• 欢迎访问web前端中文站,JavaScript,CSS3,HTML5,web前端demo
  • 如果您觉得本站非常有看点,那么赶紧使用Ctrl+D 收藏web前端中文站吧

Android热修复升级原理和实践

JAVA web前端中文站 2年前 (2017-05-21) 1013次浏览 已收录 0个评论

前段时间,Android 平台上涌现了一系列热修复方案,如阿里的 Andfix、微信的 Tinker、QQ 空间的 Nuva、手 Q 的 QFix 等等。

更多精彩内容请看 web 前端中文站
http://www.lisa33xiaoq.net 可按 Ctrl + D 进行收藏

其中,Andfix 的即时生效令人印象深刻,它稍显另类,并不需要重新启动,而是在加载补丁后直接对方法进行替换就可以完成修复,然而它的使用限制也遭遇到更多的质疑。

我们也对代码的 native 替换原理重新进行了深入思考,从克服其限制和兼容性入手,以一种更加优雅的替换思路,实现了即时生效的代码热修复。

Andfix 回顾

我们先来看一下,为何唯独 Andfix 能够做到即时生效呢?

原因是这样的,在 app 运行到一半的时候,所有需要发生变更的 Class 已经被加载过了,在 Android 上是无法对一个 Class 进行卸载的。而腾讯系的方案,都是让 Classloader 去加载新的类。如果不重启,原来的类还在虚拟机中,就无法加载新类。因此,只有在下次重启的时候,在还没走到业务逻辑之前抢先加载补丁中的新类,这样后续访问这个类时,就会 Resolve 为新的类。从而达到热修复的目的。

Andfix 采用的方法是,在已经加载了的类中直接在 native 层替换掉原有方法,是在原来类的基础上进行修改的。我们这就来看一下 Andfix 的具体实现。

其核心在于 replaceMethod 函数

Android 热修复升级原理和实践

这是一个 native 方法,它的参数是在 Java 层通过反射机制得到的 Method 对象所对应的 jobject。src 对应的是需要被替换的原有方法。而 dest 对应的就是新方法,新方法存在于补丁包的新类中,也就是补丁方法。

Android 热修复升级原理和实践

Android 的 java 运行环境,在 4.4 以下用的是 dalvik 虚拟机,而在 4.4 以上用的是 art 虚拟机。

Android 热修复升级原理和实践

我们以 art 为例,对于不同 Android 版本的 art,底层 Java 对象的数据结构是不同的,因而会进一步区分不同的替换函数,这里我们以 Android 6.0 为例,对应的就是 replace_6_0。

Android 热修复升级原理和实践

每一个 Java 方法在 art 中都对应着一个 ArtMethod,ArtMethod 记录了这个 Java 方法的所有信息,包括所属类、访问权限、代码执行地址等等。

通过env->FromReflectedMethod,可以由 Method 对象得到这个方法对应的 ArtMethod 的真正起始地址。然后就可以把它强转为 ArtMethod 指针,从而对其所有成员进行修改。

这样全部替换完之后就完成了热修复逻辑。以后调用这个方法时就会直接走到新方法的实现中了。

虚拟机调用方法的原理

为什么这样替换完就可以实现热修复呢?这需要从虚拟机调用方法的原理说起。

在 Android 6.0,art 虚拟机中 ArtMethod 的结构是这个样子的:

Android 热修复升级原理和实践

Android 热修复升级原理和实践

这其中最重要的字段就是 entry_point_from_interprete_ 和 entry_point_from_quick_compiled_code_ 了,从名字可以看出来,他们就是方法的执行入口。我们知道,Java 代码在 Android 中会被编译为 Dex Code。

art 中可以采用解释模式或者 AOT 机器码模式执行。

解释模式,就是取出 Dex Code,逐条解释执行就行了。如果方法的调用者是以解释模式运行的,在调用这个方法时,就会取得这个方法的 entry_point_from_interpreter_,然后跳转过去执行。

而如果是 AOT 的方式,就会先预编译好 Dex Code 对应的机器码,然后运行期直接执行机器码就行了,不需要一条条地解释执行 Dex Code。如果方法的调用者是以 AOT 机器码方式执行的,在调用这个方法时,就是跳转到 entry_point_from_quick_compiled_code_ 执行。

那我们是不是只需要替换这几个 entry_point_*入口地址就能够实现方法替换了呢?

并没有这么简单。因为不论是解释模式或是 AOT 机器码模式,在运行期间还会需要用到 ArtMethod 里面的其他成员字段。

就以 AOT 机器码模式为例,虽然 Dex Code 被编译成了机器码。但是机器码并不是可以脱离虚拟机而单独运行的,以这段简单的代码为例:

Android 热修复升级原理和实践

编译为 AOT 机器码后,是这样的:

Android 热修复升级原理和实践

这里面我去掉了一些校验之类的无关代码,可以很清楚看到,在调用一个方法时,取得了 ArtMethod 中的 dex_cache_resolved_methods_,这是一个存放 ArtMethod*的指针数组,通过它就可以访问到这个 Method 所在 Dex 中所有的 Method 所对应的 ArtMethod*。

Activity.onCreate 的方法索引是 70,由于是 64 位系统,因此每个指针的大小为 8 字节,又由于 ArtMethod*元素是从这个数组的第 0x2 个位置开始存放的,因此偏移(70 + 2) * 8 = 576 的位置正是 Activity.onCreate 的 ArtMethod 指针。

这是一个比较简单的例子,而在实际代码中,有许多更为复杂的调用情况。很多情况下还需要用到 dex_code_item_offset_ 等字段。由此可以看出,AOT 机器码的执行过程,还是会有对于虚拟机以及 ArtMethod 其他成员字段的依赖。

因此,当把一个旧方法的所有成员字段换成都新方法后,执行时所有数据就可以保持和新方法的一致。这样在所有执行到旧方法的地方,会取得新方法的执行入口、所属 class、方法索引号以及所属 dex 信息,然后像调用旧方法一样顺滑地执行到新方法的逻辑。

限于篇幅,本文就到这里。下一章我们将继续解剖 Android 热修复升级的?兼容性问题的根源。附上剩余的 2 篇文章地址:

【注:本文源自网络文章资源,由站长整理发布】


web 前端中文站 , 版权所有丨如未注明 , 均为原创丨本网站采用BY-NC-SA协议进行授权
转载请注明原文链接:Android 热修复升级原理和实践
喜欢 (0)
发表我的评论
取消评论
表情 贴图 加粗 删除线 居中 斜体 签到

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址