Android 构建流程(笔记流)
Android的Apk构建流程主要包括资源的编译和代码的编译。梳理清楚打包构建过程能够帮助我们理解为什么编译耗时久以及如何去优化。我们可以从以下两个方面去理解Android的Apk打包构建的过程:
- Apk构建步骤
- Apk自动化构建中的Gradle Task
Apk构建步骤
Apk的构建主要包括以下几个步骤:
- 资源文件的编译,包括通过aapt工具编译(AndroidManifest.xml,res)等资源,通过AIDL工具处理AIDL文件生成Java文件。
- Java & kotlin 源代码编译。
- 代码混淆,以及利用dex 工具将 class 文件编译为 dex 文件。
- 通过 apkbuilder / zipflinger 生成 apk文件。
- 使用zipalign 对齐,提升 mmap 访问apk文件的速度。
- 利用 apksigner 对apk文件进行签名。
资源文件的编译
可以分析一下一个Android工程中通常会包括哪些资源文件:
- AndroidManifest.xml
- res/ 文件夹下的文件
- asset/ 文件夹下的文件
在编译资源文件时,除了 asset/ 文件夹下的文件会直接保留之外,xml 与 res/下的文件都会通过 aapt2 来编译为二进制文件。
AAPT2
(Android Asset Packaging Tool2
)是一种构建工具,Android Studio
和 Android Gradle
插件使用它来编译和打包应用的资源。AAPT2
会解析资源、为资源编制索引,并将资源编译为针对 Android
平台进行过优化的二进制格式。Android Gradle 插件 3.0.0 及更高版本默认情况下会启用 AAPT2,因此通常不需要自行调用 aapt2
。
其路径位于:android_sdk/build-tools/version/
下
AAPT2 支持通过启用增量编译实现更快的资源编译。这是通过将资源处理拆分为两个步骤来实现的:
这种拆分方式有助于提高增量编译的性能。例如,如果某个文件中有更改,只需要重新编译该文件。
编译过程:
- 针对位于
res/values/
下的 xml资源文件,会被编译为以*.arsc.flat
作为扩展名的资源表。 - 其他资源文件中,其他所有的xml文件都将转换为扩展名为
*.flat
的二进制 XML 文件。而对于png图片,则会被压缩,并采用*.png.flat
作为扩展名。
1 |
|
链接过程:
AAPT2 输出的文件不是可执行文件,必须在链接阶段添加这些二进制文件作为输入来生成 APK,在链接阶段,AAPT2 会合并在编译阶段生成的所有中间文件(如资源表、二进制 XML 文件和处理过的 PNG 等 .flat文件),并将它们打包成一个 APK。而且,这个过程中还会生成 R.java文件、resources.arsc和ProGuard 规则文件,此时生成的apk 不含 Dex 等源码文件,无法安装到设备使用。
1 |
|
R.java 文件提供资源文件的id进行索引,而 resources.arsc 文件是资源索引表,供在程序运行时根据id索引到具体的资源路径。如下表所示, resources.arsc 保存了id 、name和路径的映射关系。
resources.arsc 文件的格式:
文件头:描述整个文件的信息,数据结构为 Restable_header (ResourceTypes.h)
全局字符常量池:存放所有的字符串,便于字符资源的复用。
资源包:多个资源包,系统资源应用资源等。
AIDL文件是一种用于进程间通信的接口文件,最终还是要生成为java文件,位于android_sdk/build-tools/version/
的 aidl 工具 会将 aidl文件编译为java文件。
Java & kotlin 源代码编译
Javac 编译 所有Java代码,包括 AIDL生成的java文件,生成 .class文件。kotlinc 编译所有的kotin代码,生成 .class 文件。APT/KAPT生成的代码也是位于这个阶段,当注解被标记为 AnnotationTarget.CLASS
时,相对应的注解处理器会在该阶段生成对应的java文件和class文件。
代码混淆 D8
在 AGP 3.X
以后,Google
分别引入 D8
编译器和 R8
工具作为默认的 DEX
编译器和混淆压缩工具。
d8
是一种命令行工具,位于android_sdk/build-tools/version/
,Android Studio 和 Android Gradle 插件使用该工具来将项目的.class字节码文件编译为在 Android 设备上运行的 DEX 字节码,即dexing
过程。
R8
是 ProGuard
的替代工具,用于代码的压缩(shrinking
)和混淆(obfuscation
)。
在 AGP3.4.0
版本中,R8
把 desugaring
、shrinking
、obfuscating
、optimizing
和 dexing
都合并到一步进行执行。
脱糖(desugaring
)即在编译阶段将在语法层面一些底层字节码不支持的特性转换为基础的字节码结构,(比如 List
上的泛型脱糖后在字节码层面实际为 Object
),新的语法可以在所有的设备上运行。
可以在 gradle.properties 中配置相关属性,来禁用D8、R8 和 desugaring等过程。
1 |
|
d8还可用于增量构建,d8
在执行增量构建时,会将一些额外的信息存储在 DEX 输出中。随后在完整构建应用时,d8
会利用这些信息来正确处理 --main-dex-list
选项以及合并 DEX 文件。
apkbuilder / zipflinger 生成 apk
将manifest
文件、resources
文件、dex
文件、assets
文件等等打包成一个压缩包,也就是apk
文件。老版本使用 apkbuilder ,在AGP3.6.0
之后,使用zipflinger
作为默认打包工具来构建APK
,以提高构建速度。
zipalign 对齐
zipalign
是一种 zip 归档文件对齐工具,有助于确保归档文件中的所有未压缩文件相对于文件开头对齐。这样一来,您便可直接通过 mmap 访问这些文件,而无需在 RAM 中复制这些数据并减少了应用的内存用量。
为了实现对齐,zipalign
会更改 zip 本地文件标头部分中 "extra"
字段的大小。此过程还会更改 "extra"
字段中的现有数据。
1 |
|
对齐的主要过程是将APK
包中所有的资源文件距离文件起始偏移为4字节整数倍,对齐后就可以使用mmap
函数读取文件,可以像读取内存一样对普通文件进行操作。
apksigner 签名
需要对apk文件进行签名,否则无法安装,实际项目中会有 debug 和release 两种不同的签名文件。jarsigner
只能进行v1
签名,而apksigner
可以进行v2
、v3
、v4
签名。
详细介绍:Android开发应该知道的签名知识! - 掘金 (juejin.cn)
消息摘要:对数据进行单向hash,得到固定hash值的过程。常见的摘要算法都有 MD5、SHA-1 和 SHA-256,满足 对相同内容多次hash结果一致,且很少发生hash碰撞。
非对称加密:非对称加密是使用公钥/私钥中的公钥来加密明文,然后使用对应的私钥/私钥来解密密文的过程。例如 https 通信过程中的非对称加密过程。
数字证书:由 CA机构颁发的证明公钥的身份的证书。
Android 中数字签名的生成和普通的数字签名并没有很大的区别。Android主要使用自签名的方式,不需要CA机构颁发的数字证书。
v1 签名:
利用META-INFO
文件夹中以MF
、SF
和 RSA
为扩展名的三个文件,将apk
中除了META-INFO
文件夹中的所有文件进行进行摘要写到 META-INFO/MANIFEST.MF
;然后计算MANIFEST.MF
文件的摘要写到CERT.SF
;最后计算CERT.SF
的摘要,使用私钥计算签名,将签名和开发者证书写到CERT.RSA
。
v1签名的问题:
签名校验慢。
META-INFO
文件夹不会被签名,存在一定安全隐患
v2签名:
Apk本质上为一个压缩包,而压缩包文件格式一般分为三块:文件数据区,中央目录,中央目录结束节。V2
要做的就是,在文件中插入一个APK Signing Block
,位于中央目录部分之前。
Apk自动化构建中的Gradle Task
1 |
|
1 |
|
Android Gradle Plugin源码阅读准备
Gradle - renxhui的专栏 - 掘金 (juejin.cn)
Android gradle plugin 的源码的依赖:
1 |
|
在 gradle 7.0 之前,gradle 的版本和 agp 的版本号不一致,以下是对应关系:
安利查看 AGP
源码的一个方法,直接在项目依赖对应 AGP
版本即可,不需要手动下载源码
implementation "com.android.tools.build:gradle:7.1.2"
sync 完成之后,才可以看到AGP源码
全局搜索 AppPlugin.java 类,而不是 AppPlugin.kt 文件,可以看到 com.android.application.properties 文件中记录的implementation-class对应的实现类。
1 |
|
BasePlugin.java
app 构建流程包括:
configureProject
configureExtension
createTasks
createTasksBeforeEvaluate
createAndroidTasks
经过代码跟踪,最终到ApplicationTaskManager
类中完成了整个打包过程task 的构建。对于源码阅读,只需要在用到时查找某个功能的使用节点时在跟进代码,找到具体位置即可,例如想知道自定义的 tranform 是在哪个task 任务中被执行的,就可以从该入口进入进行查找:TaskManager#createPostCompilationTasks,关于这个方法的注释为:
Creates the post-compilation tasks for the given Variant. These tasks create the dex file from the .class files, plus optional intermediary steps like proguard and jacoco
为给定变体创建编译后任务。
这些任务从.class文件创建 dex 文件,以及可选的中间步骤,如 proguard 和 jacoco
agp 各个版本 api 变化较大,最新版本甚至都用kotlin 重写了整个流程,但是整个打包的主体流程是不变的。所以,我们只需要选择其中一个版本,分析它打包流程中所对应的各个task 的作用即可。
参考资料: