❤️Android APK的诞生 ❤️ 只需两幅图(祝大家国庆快乐)

1,693

小知识,大挑战!本文正在参与「程序员必备小知识」创作活动

本文已参与 「掘力星计划」 ,赢取创作大礼包,挑战创作激励金。

前言

在分析安装过程之前,需要先了解一下 Android 项目是如何经过编译->打包生成最终的 .apk(.aab) 格式的安装包。谷歌有一张官方图片来描述 apk 的打包流程,如下图所示。

Android 应用模块的构建过程(如上图所示)遵循以下一般步骤:

  • 1、编译器将你的源代码转换为 DEX(Dalvik 可执行文件)文件,其中包括在 Android 设备上运行的字节码,以及其他所有内容到编译资源中。
  • 2、APKPackager将DEX文件和编译后的资源组合成一个APK。但是,在将你的应用安装并部署到Android设备之前,必须对APK进行签名。
  • 3、APKPackager使用调试或发布密钥库对你的 APK 进行签名:
    • 3.1如果你正在构建应用程序的调试版本,即你打算仅用于测试和分析的应用程序,则打包程序会使用调试密钥库对你的应用程序进行签名。Android Studio 使用调试密钥库自动配置新项目。
    • 3.2如果你正在构建你打算在外部发布的应用程序的发布版本,则打包程序会使用发布密钥库对你的应用程序进行签名。
  • 4、在生成最终的 APK 之前,打包程序使用zipalign工具来优化你的应用程序,以便在设备上运行时使用更少的内存。

在构建过程结束时,你将拥有应用的调试 APK 或发布 APK,可用于部署、测试或发布给外部用户。

以上是官方介绍。下面咱开始自己的理解。

开始新项目时,Android Studio 会自动为您创建其中的部分文件,并为其填充合理的默认值。所以不管一个完整的 Android 项目可能包含多个 module,而从宏观上看每一个 module 中的内容可以分为 2 部分:

  • Resources 资源文件

  • Java 或者 Kotlin 源代码

因此整个项目的编译打包过程也是针对这 2 部分来完成,如下图:

编译阶段

Resources 资源文件

资源文件包括项目中 res 目录下的各种 XML 文件、动画、drawable 图片、音视频等。AAPT 工具负责编译项目中的这些资源文件,所有资源文件会被编译处理,XML 文件(drawable 图片除外)会被编译成二进制文件,所以解压 apk 之后无法直接打开 XML 文件。但是 assets 和 raw 目录下的资源并不会被编译,会被原封不动的打包到 apk 压缩包中。

资源文件编译之后的产物包括两部分:resources.arsc 文件和一个 R 文件。前者保存的是一个资源索引表,后者定义了各个资源 ID 常量。这两者结合就可以在代码中找到对应的资源引用。如 下图 文件:

可以看出,R 文件 中的资源 ID 是一个 4 字节的无符号整数,用 16 进制表示。其中,最高的 1 字节表示 Package ID,次高的 1 个字节表示 Type ID,最低的 2 字节表示 Entry ID。

resources.arsc 相当于一个资源索引表,也可以理解为一个 map 映射表。其中 map 的 key 就是 R.java 中的资源 ID,而 value 就是其对应的资源所在路径。实际上 resources.arsc 里面还有其他信息,关于 resource.arsc 的解析可以参考 解析编译之后的Resource.arsc文件格式。

源码部分

项目中的源代码首先会通过 javac 编译为 .class 字节码文件,然后这些 .class 文件连同依赖的三方库中的 .class 文件一同被 dx 工具优化为 .dex 文件。如果有分包,那么也可能会生成多个 .dex 文件。

实际上源代码文件也包括 AIDL 接口文件编译之后生成的 .java 文件,Android 项目中如果包含 .aidl 接口文件,这些 .aidl 文件会被编译成 .java 文件。

打包阶段

最后使用工具 APK Builder 将经过编译之后的 resource 和 .dex 文件一起打包到 apk 中,实际上被打包到 apk 中的还有一些其他资源,比如 AndroidManifest.xml 清单文件和三方库中使用的动态库 .so 文件。

APK文件结构

APK(Android Package),APK 文件由一个 Zip 压缩文件组成,其中包含构成应用的所有文件。这些文件包括 Java 类文件、资源文件和包含已编译资源的文件。其文件结构如下:

  • APK 包含以下目录:

    • META-INF/:包含 CERT.SF 和 CERT.RSA 签名文件,以及 MANIFEST.MF 清单文件。系统安装APK时,应用管理器会按照对应算法对包里文件做校验,如果校验结果与META-INF中内容不一致,则不会安装这个APK。
    • assets/:包含应用的资源;应用可以使用 AssetManager 对象检索这些资源。
    • res/:包含未编译到 resources.arsc 中的资源。
    • lib/:包含特定于处理器软件层的已编译代码。此目录包含每种平台类型的子目录,如 armeabi、armeabi-v7a、arm64-v8a、x86、x86_64 和 mips。
  • APK 还包含以下文件。在这些文件中,只有 AndroidManifest.xml 是必需的。

    • resources.arsc:包含已编译的资源。此文件包含 res/values/ 文件夹的所有配置中的 XML 内容。打包工具会提取此 XML 内容,将其编译为二进制文件形式,并压缩内容。此内容包括语言字符串和样式,以及未直接包含在 resources.arsc 文件中的内容(例如布局文件和图片)的路径。
    • classes.dex:包含以 Dalvik/ART 虚拟机可理解的 DEX 文件格式编译的类。
    • AndroidManifest.xml:包含核心 Android 清单文件。此文件列出了应用的名称、版本、访问权限和引用的库文件。该文件使用 Android 的二进制 XML 格式。

apk 创建好之后,还不能直接使用。需要使用工具 jarsigner 对其进行签名,因为 Android 系统不会安装没有进行签名的程序。签名之后会生成 META_INF 文件夹,此文件夹中保存着跟签名相关的各个文件。

PackageManagerService(PKMS) 在安装过程中会检查 apk 中的签名证书的合法性。

常理来说,签名之后的 apk 应该是可以正常安装使用了,但是实际打包过程还会多一步使用工具 zipalign 对 apk 优化操作。

zipalign:是一种 zip 归档文件对齐工具。它对 apk 中的未压缩资源(图片、视频等)进行对齐操作,相对于文件开头都是对齐的。这样一来,你便可直接通过 mmap(2) 访问这些文件,而无需在 RAM 中复制相关数据并减少了应用的内存用量。

在将 APK 文件分发给最终用户之前,应该先使用 zipalign 进行优化。如果你使用 Android Studio 进行构建,则此步骤会自动完成。

mmap(2):mmap, munmap - 将文件或设备映射或取消映射到内存中。

至此一个完整的 apk 安装包就创建成功。

整个编译打包流程可以用下图来描述:

Apk打包流程.png

  • AIDL:AIDL 是 Android 中 IPC(进程间通信)方式中的一种,AIDL的作用是让让你可以在自己的 APP 里绑定一个其他 APP 的Service,这样你的 APP 可以和其他 APP 交互。注意:只有在需要不同应用的客户端通过 IPC 方式访问服务,并且希望在服务中进行多线程处理时,你才有必要使用 AIDL。如果你无需跨不同应用执行并发 IPC,则应通过实现 Binder 来创建接口;或者,如果你想执行 IPC,但不需要处理多线程,请使用 Messenger 来实现接口。

无论如何,在实现 AIDL 之前,请您务必理解绑定服务(绑定服务是 Service 类的实现,可让其他应用与其进行绑定和交互)。

  • aapt2 compile:将 res 资源编译成 .flat 的二进制文件。

  • aapt2 link:将 .flat 和 AndroidManifest 进行连接,转化成不包含 dex 的 apk 和 R.java:

  • javac:将所有 .java 文件 (包括 R 文件和 aidl 生成的 .java 文件),通过 javac 工具生成 .class 文件。

  • dx(d8):.class 文件连同依赖的三方库中的 .class 文件一同被 dx(d8) 工具优化为 **.dex 文件。

Apk 已经打包好了。后面咱们看看 Android 应用是如何启动的

相关推荐

❤️Android 包体积优化❤️

❤️Android APK的诞生 ❤️ 只需两幅图

❤️Android 从setContentView开始 ❤️ 了解(Window|Activity|View)