专栏名称: 蚂蚁金服ProtoTeam
数据前端团队
目录
相关文章推荐
51好读  ›  专栏  ›  蚂蚁金服ProtoTeam

Android热修复升级探索——代码修复冷启动方案

蚂蚁金服ProtoTeam  · 掘金  · 前端  · 2017-12-07 08:31

正文

请到「今天看啥」查看全文


Dalvik下冷启动实现

插桩实现的前因后果

众所周知, 如果仅仅把补丁类打入补丁包中而不做任何处理的话, 那么运行时类加载的时候就会异常退出, 接下来先来看下抛这个异常的前因后果。

加载一个dex文件到本地内存的时候, 如果不存在odex文件, 那么首先会执行dexopt, dexopt的入口在davilk/opt/OptMain.cpp的main方法, 最后调用到verifyAndOptimizeClass执行真正的verify/optimize操作。

_1

apk第一次安装的时候, 会对原dex执行dexopt, 此时假如apk只存在一个dex, 所以dvmVerifyClass(clazz)结果为true。 所以apk中所有的类都会被打上CLASS_ISPREVERIFIED标志,接下来执行dvmOptimizeClass, 类接着被打上CLASS_ISOPTIMIZED标志。

  • dvmVerifyClass: 类校验, 类校验的目的简单来说就是为了防止类被篡改校验类的合法性。 此时会对类的每个方法进行校验, 这里我们只需要知道如果类的所有方法中直接引用到的类(第一层级关系,不会进行递归搜索)和当前类都在同一个dex中的话, dvmVerifyClass就返回true。
  • dvmOptimizeClass: 类优化, 简单来说这个过程会把部分指令优化成虚拟机内部指令, 比如方法调用指令: invoke-
    指令变成了invoke-
    -quick, quick指令会从类的vtable表中直接取, vtable简单来说就是类的所有方法的一张大表(包括继承自父类的方法)。因此加快了方法的执行速率。

现在假如A类是补丁类, 所以补丁A类在单独的dex中。 类B中的某个方法引用到补丁类A, 所以执行到该方法会尝试解析类A。

_2

上面的代码很容易看出来, 类B由于被打上了CLASS_ISPREVERIFIED标志, 接下来referrer是类B, resClassCheck是补丁类A, 他们属于不同的dex, 所以dvmThrowIllegalAccessError。 为了解决这个问题, 一个单独无关帮助类放到一个单独的dex中, 原dex中所有类的构造函数都引用这个类,一般的实现方法都是侵入dex打包流程, 利用.class字节码修改技术, 在所有.class文件的构造函数中引用这个帮助类, 插桩由此而来。 根据前面的介绍, dexopt过程中dvmVerifyClass类校验返回false, 原dex中所有的类都没有CLASS_ISPREVERIFIED标志, 因此解决运行时这个异常。

但是插桩是会给类加载效率带来比较严重的影响的。 熟悉Dalvik虚拟机的同学知道, 一个类的加载通常有三个阶段, dvmResolveClass->dvmLinkClass->dvmInitClass, 这个三个阶段不一一详细进行说明。 dvmInitClass阶段在类解析完毕尝试初始化类的时候执行, 这个方法主要完成父类的初始化,当前类的初始化, static变量的初始化赋值等等操作。

_3

可以看到除了上面说的类初始化之外, 如果类没被打上CLASS_ISPREVERIFIED/CLASS_ISOPTIMIZED标志, 那么类的Verify和Optimize都将在类的初始化阶段进行。 正常情况下类的Verify和Optimize都仅仅只是在apk第一次安装执行dexopt的时候进行, 类的Verify实际上是很重的, 因为会对类的所有方法中的所有指令都进行校验, 单个类加载来看类Verify并不耗时, 但是如果同一时间点加载大量类的情况下, 这个耗时就会被放大。 所以这也是插桩给类的加载效率带来比较大影响的后果, 接下来来看下具体会给类加载带来多大的影响。







请到「今天看啥」查看全文