知道了 native 替换方式兼容性问题的原因,我们是否有办法寻求一种新的方式,不依赖于 ROM 底层方法结构的实现而达到替换效果呢?
我们发现,这样 native 层面替换思路,其实就是替换 ArtMethod 的所有成员。那么,我们并不需要构造出 ArtMethod 具体的各个成员字段,只要把 ArtMethod 的作为整体进行替换,这样不就可以了吗?
也就是把原先这样的逐一替换。
变成了这样的整体替换。
因此 Andfix 这一系列繁琐的替换:
其实可以浓缩为:
就是这样,一句话就能取代上面一堆代码,这正是我们深入理解替换机制的本质之后研发出的新替换方案。
刚才提到过,不同的手机厂商都可以对底层的 ArtMethod 进行任意修改,但即使他们把 ArtMethod 改得六亲不认,只要我像这样把整个 ArtMethod 结构体完整替换了,就能够把所有旧方法成员自动对应地换成新方法的成员。
但这其中最关键的地方,在于 sizeof(ArtMethod)。如果 size 计算有偏差,导致部分成员没有被替换,或者替换区域超出了边界,都会导致严重的问题。
对于 ROM 开发者而言,是在 art 源代码里面,所以一个简单的 sizeof(ArtMethod)就行了,因为这是在编译期就可以决定的。
但我们是上层开发者,app 会被下发给各式各样的 Android 设备,所以我们是需要在运行时动态地得到 app 所运行设备上面的底层 ArtMethod 大小的,这就没那么简单了。
想要忽略 ArtMethod 的具体结构成员直接取得其 size 的精确值,我们还是需要从虚拟机的源码入手,从底层的数据结构及排列特点探寻答案。
在 art 里面,初始化一个类的时候会给这个类的所有方法分配空间,我们可以看到这个分配空间的地方:
类的方法有 direct 方法和 virtual 方法。direct 方法包含 static 方法和所有不可继承的对象方法。而 virtual 方法就是所有可以继承的对象方法了。
AllocArtMethodArray 函数分配了他们的方法所在区域。
可以看到,ptr 是这个方法数组的指针,而方法是一个接一个紧密地 new 出来排列在这个方法数组中的。这时只是分配出空间,还没填入真正的 ArtMethod 的各个成员值,不过这并不影响我们观察 ArtMethod 的空间结构。
正是这里给了我们启示,ArtMethod 们是紧密排列的,所以一个 ArtMethod 的大小,不就是相邻两个方法所对应的 ArtMethod 的起始地址的差值吗?
正是如此。我们就从这个排列特点入手,自己构造一个类,以一种巧妙的方式获取到这个差值。
由于 f1 和 f2 都是 static 方法,所以都属于 direct ArtMethod Array。由于 NativeStructsModel 类中只存在这两个方法,因此它们肯定是相邻的。
那么我们就可以在 JNI 层取得它们地址的差值:
然后,就以这个 methSize 作为 sizeof(ArtMethod),代入之前的代码。
问题就迎刃而解了。
值得一提的是,由于忽略了底层 ArtMethod 结构的差异,对于所有的 Android 版本都不再需要区分,而统一以 memcpy 实现即可,代码量大大减少。即使以后的 Android 版本不断修改 ArtMethod 的成员,只要保证 ArtMethod 数组仍是以线性结构排列,就能直接适用于将来的 Android 8.0、9.0 等新版本,无需再针对新的系统版本进行适配了。事实也证明确实如此,当我们拿到 Google 刚发不久的 Android O(8.0)开发者预览版的系统时,hotfix demo 直接就能顺利地加载补丁跑起来了,我们并没有做任何适配工作,鲁棒性极好。
【注:本文源自网络文章资源,由站长整理发布】
web 前端中文站 , 版权所有丨如未注明 , 均为原创丨本网站采用BY-NC-SA协议进行授权
转载请注明原文链接:详解 Android 热更新升级如何突破底层结构差异?