热修复技术的诞生,是源于传统版本发布流程无法满足开发者对于即时修复bug的需求从而产生的。热修复的优势在于无需发布新的版本,用户不会感知bug修复。这样可以将bug修复的代价降到最低。

      热修复分为热替代修复和冷启动修复,其中热替代修复体验和实时性会更好,但是相应的,限制会更高。而冷启动修复几乎能满足所有的热修复要求。

      热修复主要有三个方面,分别为代码热修复,资源热修复以及so文件热修复。在接下来的章节中,我们会进行介绍。热修复会比较涉及Android底层的一些东西。因此,在进行讲述之前,我们会介绍一下前导知识。如果是有经验的开发者可以跳过这一章。

    首先,请回答三个问题:APK是什么?Dex字节码是怎么执行的?App的启动流程是什么?一个一个来。

    (1) APK是什么?

       APK是一个zip格式封装的资源包。我们将它解压后可以看到下图。

android art 冷修复 android热修复_Android

         lib包存放JNI相关的so库。JNI用于java和c++的通信,方便开发者调用底层的接口。

        assets中存放资源文件。与resources中的资源文件相比,assets中的资源文件不会进行压缩处理,不会生成资源ID,所以可以在assets中随意建子目录,在应用时使用完整的文件路径。

       classes.dex是Android项目中所有Java代码最终编译的最终形式。项目的编译会经过.java => .class =>.dex的流程。不同于传统的jvm,Android的Davilk/Art虚拟机运行的是.dex文件。.dex文件中有最大方法数的限制,因此一般企业级项目的apk中都有多个.dex文件。热修复的代码修复方法就是对这里的.dex文件进行处理,从而达到修改代码的目的。

       res中存放了xml,图片等资源文件。而resources.arsc文件则包含了这些文件的资源Id。在使用时,系统先拿到这些资源id,然后在res中获得想要的资源文件。热修复中需要处理的点就是如何处理arsc的资源id,从而使得应用能够获得补丁包中的资源。

      META-INF中存放着签名相关的文件,用于校验文件的合法性。

      AndroidManifest应该是每个开发者最熟知的文件了,用于对整个app的配置,注册四大组件以及申请权限等等。需要注意的一点时,App在启动时,系统会首先获取AndroidManifest.xml文件,而不会给任何预先访问补丁包的机会,因此AndroidManifest.xml的修复是做不到。我们通常预先在里面埋桩,用来在需要添加新的四大主键,实现新主键的注册。

    (2) Dex字节码是怎么执行的

        Dex文件是Android虚拟机最终运行的程序。google官方文档在这里

        这里大致讲一下。一个Dex文件主要由以下几部分组成。

  1. header:标头。标头分为很多部分,用于识别dex文件是否有效,校验文件是否损坏,校验安全性,存储文件大小,静态偏移等等。
  2. string_ids:类型标志符列表。这个列表里存储了字符串的数量和偏移距离。系统可以取值做对应偏移,从而获得真实的字符串。当然这些字符串也是以16进制的形式存储的。
  3. type_ids:类型标识符列表。这些是此文件引用的所有类型(类、数组或原始类型)的标识符(无论文件中是否已定义)。简单来说,就是系统从中获取当前定义的类的数量,以及这些类的偏移量,从而根据偏移量找到这些类的定义字节码。这些类中有字符串和方法的话,就会存有这些对象的索引。
  4. proto_ids:方法原型标识符列表。它存储了dex中方法原型的个数和位置偏移。什么是方法原型?你可以理解为是方法的声明,包括了返回值和参数。但是没有方法体。
  5. field_ids:字段标识符列表。这里存储了程序中的变量类型和变量名的索引。这里要说一下,这里的索引是对其余表的索引。例如获取标志索引是1,那么系统获取上面type_ids中为索引为1的类型。
  6. method_ids:方法标识符列表。这些是此文件引用的所有方法的标识符(无论文件中是否已定义)。这里存储了方法所在的类,方法原型,以及方法名的索引。这里是热修复重点处理的列表。系统常常会获取其中的方法数量和偏移位置,根据偏移位置进行移动,找到程序中所有的方法。这里每一个方法的方法所在的类,方法原型,以及方法名的索引都是由16进制的方法进行存储。
  7. class_defs:类定义列表。主要是类的相关信息。主要是访问权限,继承关系,接口,源文件名,注解,类数据等等。
  8. call_site_ids:调用站点标识符列。
  9. method_handles:方法句柄列表
  10. data:数据区,包含上面所列表格的所有支持数据。
  11. link_data:静态链接文件中使用的数据。

    我们主要关注前7个列表,分析前七个列表,大致就能从字节码中还原出整个程序。系统在执行时还要做很多的操作,不只是解析执行字节码,同时也要调用堆栈,申请空间,这里只是简单来说一下。

(3)App启动流程是什么?

        先从Android的启动流程说起。init是Android用户空间的第一个进程。当手机开启时,引导程序bootloader启动,运行linux内核。内核会启动一个名叫init的文件。至此init进程启动。init会启动一个名叫Zygote的进程,它初始化了第一个androdi虚拟机,它是所有Android 程序运行的基础,加载了基础的通用资源。所有的app进程都是由它孵化出来了。而我们通常意义上所看到的Android界面则是由它孵化的第一个app:home进程。

       当我们点击手机桌面的图标的时候,App就由launcher开始启动了。launcher本身就是一个应用程序。但launcher本身不能直接启动其他的应用程序。它会通过Binder通信,调用ActivityManagerService的startActivity方法。接下来的过程就有AMS负责了程序的生命周期。它会通知Zygote进程来fork一个新的进程,有这个进程来承载我们的App的运行。

      当进程被fork出来后,就是和我们的目标App进行绑定。这里再次重申一下,App的AndroidManifext.xml文件只在App被安装时被解析一次,而后来的启动过程中并不会被解析,所以无论是热替代修复还是冷启动修复都是无法修复AndroidManifext中的bug的。

     被fork的进程会和Application绑定起来,将App的classes加载到内存当中。这里面会走Application中的代码。所以热修复sdk通常是在Application文件中进行初始化的。它是优先于整个App业务代码执行的。

    Application的启动顺序为:Application.attachBaseContext -> ContentProvider.onCreate ->Application.onCreate->Activity.onCreate。所以attachBaseContext方法是开发者所能接触到的,App启动最先调用的回调方法。

    整个前导知识部分就到这里结束了,接下来的章节,我们就开始逐步介绍热修复中的知识点。