Android Apk 编译打包流程
本文通过手动编译 apk 的形式,带你了解 Android apk 的生成流程。
Android Apk 编译打包流程
Android 项目从编译到打包流程如下(Google 官网已经找不到,取而代之是一张简化后的,但我还是更喜欢这张)

简要描述此流程使用的一些编译工具:
- aapt:aapt(Android Asset Packaging Tool) 工具会打包应用中的资源文件,如 AndroidManifest.xml、layout 布局中的 xml 等,并将 xml 文件编译为二进制形式,当然 assets 文件夹中的文件不会被编译,图片及 raw 文件夹中的资源也会保持原来的形态,(需要注意的是 raw 文件夹中的资源也会生成资源 id。aapt 编译完成之后会生成 R.java 文件)。
- aidl:AIDL 工具会将所有的 aidl 接口转化为 java 接口。
- Java Compiler(Java编译器):当 AAPT 与 AIDL 工具将需要处理的数据处理好后,Java 编译器会将所有的java代码,包括R.java与 aidl 文件编译成 .class 文件。
- dex:dex 工具会将上述产生的 .class 文件及第三库及其他 .class 文件编译成 .dex 文件(dex文件是Dalvik虚拟机可以执行的格式),dex文件最终会被打包进APK文件。
- apkbuilder:apkbuilder 工具会将编译过的资源及未编译过的资源(如图片等)以及 .dex 文件打包成APK文件。
- Jarsingner:生成 APK 文件后,需要对其签名才可安装到设备,平时测试时会使用 debug keystore,当正式发布应用时必须使用 release 版的 keystore 对应用进行签名。Jarsigner工具会根据相应的keystore生成相应的签名APK文件。
- zipalign(release mode):zipalign 工具,它能够对打包的应用程序进行优化。在你的应用程序上运行 zipalign ,使得在运行时 Android 与应用程序间的交互更加有效率。
手动编译打包流程示例
本示例所使用电脑环境
Mac m1 电脑 Android Studio Arctic Fox|2020.3.1 配置 JDK、Android SDK 环境- 准备一个 Android 项目,此项目只使用 Android-29 平台库,不做过多的依赖,文件结构如下
.├── AndroidManifest.xml // 不引用 style├── com│ └── example│ └── compile│ └── old│ └── MainActivity.java // 继承 Activity└── res ├── layout │ └── activity_main.xml // 使用 LinearyLayout ├── mipmap-xhdpi │ ├── ic_launcher.webp │ └── ic_launcher_round.webp └── values └── strings.xml- 创建 build 文件夹,后面作为输出文件夹,使用 aapt2 编译工具,将 res 目录中的资源文件编译成一个名为 res.zip 的压缩包。
mkdir buildaapt2 compile -o build/res.zip --dir resaapt2 compile -o build/res.zip --dir res命令解析
- aapt2: 这是 Android Asset Packaging Tool 2 (aapt2) 的命令行工具,用于处理 Android 应用程序资源。
- compile: 这是 aapt2 命令的子命令,指示 aapt2 编译资源文件。
- -o build/res.zip: 这个参数指定编译后的资源文件输出的位置和文件名。在这里,资源文件将被编译成一个名为res.zip的压缩包,并存储在build目录下。
- -dir res: 这个参数指定要编译的资源文件所在的目录。在这里,资源文件位于res目录下,aapt2 将编译这个目录中的资源文件。
- 对资源进行链接
aapt2 link build/res.zip -I $ANDROID_HOME/platforms/android-29/android.jar --java build --manifest AndroidManifest.xml -o build/app-debug.apkaapt2 link build/res.zip -I $ANDROID_HOME/platforms/android-29/android.jar --java build --manifest AndroidManifest.xml -o build/app-debug.apk 命令解析
- aapt2 link: 使用 aapt2 工具进行链接操作。
- build/res.zip: 要链接的资源文件(通常是资源文件的压缩包)。
- I $ANDROID_HOME/platforms/android-29/android.jar: 指定 Android 平台的 android.jar 文件,用于包含 Android 框架的类和资源。
- -java build: 生成 R.java 文件到指定的目录 build 中,这个文件包含了资源 ID 的映射。
- -manifest AndroidManifest.xml: 指定 AndroidManifest.xml 文件,这是 Android 应用程序的清单文件。
- o build/app-debug.apk: 指定输出的 APK 文件路径和名称。
- 编译 .java 文件(把 R.java 文件迁移到 MainActivity.java 所在目录,请正确指定的类路径和源代码文件路径是正确的,以确保编译过程顺利进行)
javac -d build -cp $ANDROID_HOME/platforms/android-29/android.jar com/example/compile/old/*.javajavac -d build -cp $ANDROID_HOME/platforms/android-29/android.jar com/example/compile/old/*.java 命令解析
- javac: Java 编译器命令。
- d build: 指定编译后的 .class 文件输出目录为 build 目录。
- cp $ANDROID_HOME/platforms/android-29/android.jar: 指定编译时的类路径,这里是指定了 Android 平台的 android.jar 文件,以便编译器能够找到 Android 平台的类和接口。
- com/example/compile/old/*.java: 要编译的 Java 源代码文件,这里指定了一个包路径下的所有 Java 文件进行编译。
编译后目录结构如下
.├── AndroidManifest.xml├── build│ ├── app-debug.apk│ ├── com│ │ └── example│ │ └── compile│ │ └── old│ │ ├── MainActivity.class│ │ ├── R$id.class│ │ ├── R$layout.class│ │ ├── R$mipmap.class│ │ ├── R$string.class│ │ ├── R.class│ │ └── R.java│ └── res.zip├── com│ └── example│ └── compile│ └── old│ ├── MainActivity.java│ └── R.java└── res ├── layout │ └── activity_main.xml ├── mipmap-xhdpi │ ├── ic_launcher.webp │ └── ic_launcher_round.webp └── values └── strings.xml- 生成 Dex 文件
d8 --output build/ --lib $ANDROID_HOME/platforms/android-29/android.jar build/com/example/compile/old/*.classd8 --output build/ --lib $ANDROID_HOME/platforms/android-29/android.jar build/com/example/compile/old/*.class 命令解析
- d8: 这是一个命令行工具,用于将 Java 字节码文件转换为 Dex 格式,以便在 Android 设备上运行。
- -output build/: 这个选项指定了输出目录为 build/,即将转换后的 Dex 文件输出到该目录。
- -lib $ANDROID_HOME/platforms/android-29/android.jar: 这个选项指定了 Android 平台库的路径,其中 $ANDROID_HOME 是 Android SDK 的根目录,android-29 是 Android API 版本,android.jar 是 Android 平台库文件。
- build/com/example/compile/old/*.class: 这是要转换的 Java 字节码文件的路径,通配符 .class 表示选择该目录下所有的 .class 文件。
编译后目录结构如下
.├── AndroidManifest.xml├── build│ ├── app-debug.apk│ ├── classes.dex│ ├── com│ │ └── example│ │ └── compile│ │ └── old│ │ ├── MainActivity.class│ │ ├── R$id.class│ │ ├── R$layout.class│ │ ├── R$mipmap.class│ │ ├── R$string.class│ │ ├── R.class│ │ └── R.java│ └── res.zip├── com│ └── example│ └── compile│ └── old│ ├── MainActivity.java│ └── R.java└── res ├── layout │ └── activity_main.xml ├── mipmap-xhdpi │ ├── ic_launcher.webp │ └── ic_launcher_round.webp └── values └── strings.xml- 将 dex 文件放入 apk 文件中
zip -j build/app-debug.apk build/classes.dexzip -j build/app-debug.apk build/classes.dex 参数解析
- zip: 这是用于创建和修改zip文件的命令。
- j: 这个选项表示在压缩文件时不包含目录路径,只压缩文件本身。
- build/app-debug.apk: 这是指定要创建的zip文件的路径和名称。在这里,将创建一个名为 app-debug.apk 的zip文件,并存储在 build 目录下。
- build/classes.dex: 这是指定要添加到zip文件中的文件路径和名称。在这里,classes.dex 文件将被添加到 app-debug.apk 中。
- 签名,使用 debug 签名 密码 “android”
apksigner sign -ks ~/.android/debug.keystore build/app-debug.apkKeystore password for signer #1: apksigner sign -ks ~/.android/debug.keystore build/app-debug.apk 参数解析
- apksigner: 这是用于对 APK 进行签名的工具。
- sign: 这是 apksigner 的子命令,指示对 APK 文件进行签名操作。
- ks ~/.android/debug.keystore: 这个参数指定了用于签名 APK 的密钥库文件的路径。在这里,~/.android/debug.keystore 是密钥库文件的路径。
- build/app-debug.apk: 这是指定要签名的 APK 文件的路径和名称。在这里,app-debug.apk 将被签名。
- 将最终 .apk 文件安装到模拟器或真机

通过这个示例可以看出,即使在小型项目中,编译和打包 Android 应用程序也需要耗费相当的精力。而在大型项目中,涉及数百甚至数千个资源文件、Java/Kotlin 文件,以及各种测试、Lint 检测、编译(apt、asm 等工具又会干预编译)、链接和优化等流程。此外,还要考虑到模块化、复杂的依赖关系等因素,手动编译这样的项目几乎是不可能的任务,并且手动编译容易出错、效率低下。
因此,构建工具如 Gradle 应运而生。Gradle 等构建工具能够自动化和简化整个构建流程,从而减轻开发人员的负担,并确保构建过程的可靠性和一致性。
简洁优雅如 Gradle,也不要高兴的太早,掉入 Gradle 的坑里又是另外一个故事啦。