Android打包相关知识整理(一)

一些基础概念

JVM Dalvik ART

JVM & DVM

DVM,即Dalvik,是Google开发的用于Android平台的Java虚拟机,JVM则是传统的Java虚拟机,有很多具体的版本,是跨平台的。那么他们的区别主要体现在以下几点:

  1. 从架构上看,JVM是基于栈的,DVM是基于寄存器的。
  2. JVM执行的是由Java文件编译的.class文件,而DVM则执行的是有Java文件编译为.class文件后,再次编译得到的.dex文件。
  3. JVM只能运行一个实例,若干应用程序都在同一个虚拟机中。而DVM则可以运行多个虚拟机实例,每一个app都运行在单独的虚拟机进程中。

    DVM & ART

    ART,即Android Runtime,也算是Dalvik虚拟机的升级版。从Android 4.4版本开始,系统就已经内置了ART,直到5.0开始,完全用ART替换了Dalvik。
    Dalvik安装的app,在运行时会动态的将一部分字节码解释为机器码,这一过程是即时编译,JIT(Just in time),而ART则是在一开始安装app时就将字节码转换为了机器码并存在存储相应的存储空间,这一过程是提前编译,AOT(Ahead of time)。
    因此,DVM下app运行较慢,且会频繁的访问cpu,而ART下app虽然安装时间更长且占用了更多的空间,但是启动、运行速度有了提升,节省了电量的消耗。

    JIT & AOT

    前面提到了JIT和AOT只是两种不同的编译形式,一种是即使编译,一种是提前编译。
    JIT是从Android 2.2引入的。虚拟机执行的是字节码DEX,但是机器执行的是机器码,因此每次运行程序的时候,都需要将DEX转换为本地的机器码,交给机器执行。加入JIT后,这一过程就由JIT来处理了。具体来讲,就是在程序运行中,每遇到一个新类,就会对这个类进行编译,编译后的结果转换为精简的原生指令码,这样下次执行到同样的逻辑时,直接执行这一精简原生指令码,就会提高运行速度了。但是如果对所有的类都进行JIT,那么那些执行次数较少的类就会拖慢整体的速度,因为你转换毕竟也是需要时间的嘛。所以并不是对所有代码都执行JIT的,是有选择的。此外,JIT并不是一劳永逸的,在每次运行时,都要重新进行JIT。
    AOT相对来说,则是“用空间换时间”。在第一次安装时,就已经将字节码转换为了机器码,并保存在特定的存储空间。这样会导师安装时较慢,且会占用额外的存储空间,但是好处也是很明显的,程序运行时直接执行机器码即可,运行速度有了提升。
    无论是JIT还是AOT,各有优缺点。所以Google在Android 7.0上将JIT和AOT合并起来一起用。具体来说,应用在初次安装时,不会提前编译了,因此安装速度不会降低。在应用运行时,会通过JIT优化,存储在jit code cache中,同时标记执行次数较多的函数并记录在profile文件中。等手机进入charging和idle状态时,系统会每隔一段时间扫描这些profile文件,执行AOT。以为只优化了那些热点函数,因此在初次运行时没有很明显的变化,在多次运行后,才会有速度的显著提升。同时所占用的额外存储空间也小了,安装速度也提高了。

    DEX ODEX OAT

    JVM执行的是由类文件编译出的.class文件,而Android虚拟机,执行的是由.class文件进一步处理得到的文件,这一处理过程即dx,得到的文件为.dex文件。
    所以DEX文件简单来说就是有.class文件通过dx工具转化的。一般情况下,一个项目中有很多类文件,会生成很多.class文件,这些多个.class文件会通过文件内容的优化,然后合并,最终得到一个.dex文件(在不考虑64k这种特殊情况下)。
    ODEX文件,即Optimized dex,是DEX经过优化后得到的文件。他并不是一种文件格式,即不存在什么.odex后缀,他只是一种类型,即DEX文件经过优化后的文件类型。那么具体来说,一个.dex文件在经过优化后,后缀可能还是.dex,也可能变成了新的.oat,但是他们都还是属于ODEX文件——优化后的DEX文件。
    具体来讲,我们app打包成apk后,apk作为一种压缩文件,内部包含了DEX文件。在系统首次开机时,会解压apk文件,取出其中的DEX文件,存储到系统的data/app目录下。然后,重点来了,如果当前运行的是Dalvik虚拟机,那么DEX文件会被Dalvik虚拟机进行一次优化,这次优化是通过函数dexopt执行的,优化产生的文件名还是旧的文件名,后缀还是.dex,只是他现在已经不是DEX文件,而是优化的DEX文件,也就是ODEX文件了。而如果当前系统运行的虚拟机是ART,那么DEX文件也会被ART虚拟机做一次优化,这次优化是通过dex2oat工具执行的,优化后生成的文件名还是旧的文件名,后缀变成了.oat,文件类型也变成了ODEX。两种优化方式最终都会将ODEX文件存放至系统的data/dalvik-cache目录下。
    为什么要这样处理文件名呢?因为虚拟机是从Dalvik过渡到ART上的,这样的优化后文件名不变,路径,那么任何通过绝对路径来引用ODEX文件的代码就都不用修改了。

apk

apk的内容

分析打包过程,首先看一下打包后的结果,然后逆推每一部分是如何打包的。
apk全称是Android Package,其实就是一个后缀为.apk的压缩文件,他包含了Android App包含的代码、资源的所有数据。用解压工具解压一个apk包后,可以看到目录如下:

  1. AndroidManifest.xml
    这一文件是Android应用程序的清单文件,我们平时写应用时都会和他打交道。这一文件内包含了应用程序的所有界面、权限、BroadCast等内容,以及程序自己的相关信息,这个文件相当于是应用程序用来给系统做一个自我介绍。
  2. assets
    这一目录存放了apk中的静态资源文件,需要通过AssetManager类来访问内容。
  3. classes.dex
    Android项目中的代码在编译后会生成.class文件,然后再通过dx工具转换为字节码文件,就是这个dex文件。一般情况下只有一个classes.dex,如果项目代码方法数超过了65535而采用了multidex的话,会有其他的.dex文件。
  4. META-INF
    这以目录包含了应用程序的签名信息,用来验证apk文件的完整性、合法性,帮助系统在安装应用程序时能够确保apk文件是完整、安全的,没有被破坏、修改。
  5. res
    这一目录下存放了所有的其他资源,包括像layout资源、drawable资源等,这些资源都映射到一个个资源ID,应用程序则通过资源的索引来访问这些资源文件。
  6. resources.arsc
    资源的索引,是通过资源ID来做的。这里的resources.arsc是一个资源索引表,相当于记录了资源文件路径与其对应的ID的一个表,而R.java文件,则定义了资源关联的ID。在程序中通过R文件定义的资源名称,获取资源ID,进而用资源ID在resources.arsc中获取到资源的具体路径。

apk打包流程

这是最新的官网的打包流程图:

这是旧版官网的打包流程图:

相对而已,旧版的更加详细一点。就拿旧版的打包流程图来说吧,打包主要有以下几步:

  1. 使用aapt工具处理所有的资源,生成一个R.java文件,一个resources.arsc文件以及其他资源。
  2. 处理.aidl文件,生成对应的Java接口文件。
  3. 将上述两步得到的R.java文件、Java接口文件,与Andorid源码一起,通过Java编译器,编译得到Java字节码文件.class文件。
  4. 获取依赖的第三方库文件,将其与上一步得到的.class文件一起,通过使用dx工具,生成.dex文件。
  5. 将资源索引文件resources.arsc、资源目录res、与上一步得到的.dex文件一起,通过apkbuilder工具,构建出初始的.apk文件。
  6. 使用jarsigner工具,对.apk文件进行签名。
  7. 使用zipalign工具,对.apk文件进行对齐。(让资源按四字节的边界进行对齐,加快资源的访问速度)

经过以上七步,一个完成的apk文件就诞生了。

apk安装过程

Android应用的安装涉及到以下几个目录:

  • /data/app:存放apk文件
  • /data/data:存放应用程序的数据
  • /data/dalvik-cache:存放ODEX文件

具体的安装过程如下:

  1. 将apk文件复制到系统的/data/app目录下。
  2. 解压apk文件。
  3. 通过apk文件中的签名文件,校验签名,验证apk包的合法、安全性。
  4. 解压并校验.dex文件。
  5. 将解压得到的.dex文件进行优化,产生odex文件,存储在/data/dalvik-cache目录下。
  6. 在/data/data目录下,以应用程序的包名为名,创建一个目录,用来存储应用程序的所有数据。