在 Android 开发中,构建系统是连接源代码与最终 APK 的“隐形引擎”。它不仅负责编译、打包,还支持高度定制化的扩展能力——从提取远程依赖到编译期代码注入。然而,由于其模块化设计和多阶段流程,初学者常感碎片化、难成体系。

一、整体构建流程:从 Sync 到 APK 生成
理解 Android 构建的第一步,是把握其宏观执行路径。
1. Sync 阶段:项目配置加载
当你在 build.gradle 中修改配置并点击 「Sync Now」,Android Studio 会触发 Gradle 的 配置阶段(Configuration Phase):
- Gradle 加载所有
build.gradle文件; - 执行
apply plugin: 'com.android.application'; -
源码中一定有一个
com.android.application.properties文件与之相对应,这便是 Plugin 的入口; - 最终调用
Plugin#apply()方法,注册后续构建任务。
✅ 关键点:Sync 不执行编译,只解析配置、注册 Task。这是 AGP(Android Gradle Plugin)介入的起点。
2. 构建执行阶段:Task 流水线
执行 ./gradlew assembleDebug 时,AGP 按照依赖关系依次执行一系列 Task,形成一条清晰的流水线:
| 阶段 | 核心 Task | 作用说明 |
|---|---|---|
| 资源预处理 | :app:mergeDebugResources |
使用 AAPT2 编译阶段:将 XML、图片等资源编译为二进制 Flat 文件,存于 build/intermediates/merged_res/
|
:app:processDebugManifest |
合并主模块与依赖库的 AndroidManifest.xml
|
|
:app:mergeDebugAssets / compressDebugAssets
|
合并并压缩 assets/ 目录 |
|
| 代码编译 | :app:compileDebugKotlin |
编译 Kotlin 源码为 .class
|
:app:compileDebugJavaWithJavac |
使用 javac 编译 Java 源码为 .class
|
|
| 资源链接 | :app:processDebugResources |
AAPT2 链接阶段:生成 R.java 和 resources.arsc,打包所有已编译资源 |
| 字节码处理 | 自定义 Transform(如有) | 在 .class 转 .dex 前插入字节码修改逻辑(见第四部分) |
| DEX 转换 | :app:dexBuilderDebug |
将 .class 转换为 Dalvik 可执行的 .dex 文件 |
| APK 打包 | :app:packageDebug |
将 .dex、资源、AndroidManifest.xml、assets、libs/(含 .so)打包为未签名 APK |
(Release 版本额外执行 zipalign 对齐与签名) |
Debug 版本通常不启用代码混淆与资源压缩,以加快开发迭代速度。
(2)Release 构建(assembleRelease)完整 Task 流程 ✅【新增完整列表】
Release 构建在 Debug 基础上增加了优化、混淆、对齐、签名等关键步骤,确保 APK 体积小、性能高、安全性强。以下是 assembleRelease 执行的完整核心 Task 序列(按实际依赖顺序排列):
| Task | 作用说明 |
|---|---|
:app:preBuild |
初始化构建环境 |
:app:preReleaseBuild |
Release 构建前准备 |
:app:compileReleaseAidl |
编译 AIDL 接口 |
:app:generateReleaseBuildConfig |
生成 BuildConfig.java(DEBUG=false) |
:app:generateReleaseResValues |
提取 build.gradle 中的资源值(如 versionName) |
:app:mergeReleaseResources |
AAPT2 编译阶段:合并并编译资源 → 生成 Flat 文件 |
:app:createReleaseCompatibleScreenManifests |
生成兼容屏幕密度的 Manifest(若配置) |
:app:extractDeepLinksRelease |
提取 Deep Link 配置 |
:app:processReleaseManifest |
合并所有 AndroidManifest.xml
|
:app:mergeReleaseShaders |
合并 GLSL 着色器(如有) |
:app:compileReleaseShaders |
编译着色器 |
:app:generateReleaseAssets |
生成着色器资产 |
:app:mergeReleaseAssets |
合并 assets/ 目录 |
:app:compressReleaseAssets |
压缩 assets |
:app:checkReleaseDuplicateClasses |
检查重复类(防止依赖冲突) |
:app:mergeReleaseJniLibFolders |
合并 JNI 库目录 |
:app:mergeExtDexRelease |
合并扩展 DEX(Multidex 场景) |
:app:mergeProjectDexRelease |
合并主 DEX |
:app:optimizeReleaseResources |
资源优化(若启用 shrinkResources true) |
:app:mergeReleaseNativeLibs |
合并 .so 文件到 lib/ 目录 |
:app:stripReleaseDebugSymbols |
移除 Native 库中的调试符号(减小体积) |
:app:validateSigningRelease |
验证签名配置 |
:app:writeReleaseApplicationId |
写入 Application ID |
:app:compileReleaseKotlin |
编译 Kotlin 源码 |
:app:compileReleaseJavaWithJavac |
编译 Java 源码 |
:app:transformReleaseClassesWithAsm(如有) |
自定义 ASM Transform 修改字节码 |
:app:minifyReleaseWithR8 或 :app:shrinkReleaseRes
|
代码混淆与资源缩减(若启用 minifyEnabled true)• 使用 R8(默认)或 ProGuard• 移除未使用类、方法、字段• 混淆命名(a, b, c...) |
:app:processReleaseResources |
AAPT2 链接阶段:生成 R.java 和 resources.arsc
|
:app:packageRelease |
打包未对齐、未签名的 APK |
:app:signReleaseBundle / :app:signReleaseApk
|
使用 keystore 对 APK 签名 |
:app:bundleReleaseResources(AAB) |
若构建 AAB,则打包资源 bundle |
:app:collectReleaseDependencies |
收集依赖信息 |
:app:configureReleaseDependencies |
配置依赖 |
:app:mergeReleaseGeneratedProguardFiles |
合并 ProGuard 规则文件 |
:app:packageReleaseUniversalApk(如有) |
生成 universal APK(含所有 ABI) |
:app:assembleRelease |
最终聚合 Task,标志着 Release 构建完成 |
📌 关键优化项说明:
minifyEnabled true:启用 R8/ProGuard,大幅减小 APK 体积;shrinkResources true:移除未引用的资源(需配合minifyEnabled);zipAlign:在package后自动执行(AGP 内部集成),确保内存对齐,提升运行效率;- 签名:必须提供有效的
keystore配置,否则构建失败。
✅ 示例:为何 .so 文件丢失?
Native 库(.so)在mergeReleaseNativeLibs阶段被复制到./app/build/intermediates/merged_native_libs/release/out/lib/。
若该目录为空或内容异常,.so 丢失的大部分原因是由于合并出错。
解决方案:删除merged_native_libs/和~/.gradle/caches/build-cache-1/,强制重新执行合并任务。
二、Gradle 插件与依赖管理:构建系统的扩展能力
AGP 提供了标准流程,但真实项目常需定制行为——这正是 Gradle Plugin 的价值所在。
1. 自定义 Plugin:扩展构建逻辑
通过编写自定义 Plugin,可注册新 Task、修改现有行为或注入 AOP 逻辑(见第四部分)。其核心是实现 Plugin<Project> 接口,并在 apply() 中操作 Project 对象。
2. 实战:提取远程 AAR 依赖为本地文件
场景:需要离线分发某第三方库,或审计其内容。
实现步骤:
-
在根项目
build.gradle中定义配置与任务:
allprojects {
// 定义一个新的配置用于提取AAR
configurations {
retrieveAar
}
// 清理本地目录
task cleanAars(type: Delete) {
delete "$rootDir/local-aars"
}
// 定义要提取的依赖(包括直接依赖和需要特别处理的传递依赖)
def dependenciesToExtract = [
// Room 的 runtime(直接依赖)
'android.arch.persistence.room:runtime:1.1.1',
// Core 的 runtime(传递依赖)
'android.arch.core:runtime:1.1.1',
// Room 的 runtime(直接依赖)
'android.arch.persistence.room:common:1.1.1',
// Core 的 runtime(传递依赖)
'android.arch.core:common:1.1.1'
]
// 创建任务强制解析所有依赖并提取AAR
task copyAllAars {
doLast {
def destinationDir = file("$rootDir/local-aars")
destinationDir.mkdirs()
println "开始提取所有AAR(包括传递依赖)..."
// 1. 强制解析所有依赖
def resolvedDeps = configurations.retrieveAar.resolvedConfiguration.resolvedArtifacts
// 2. 收集所有AAR文件路径
def aarFiles = [:] // 格式:依赖坐标 -> 文件路径
resolvedDeps.each { artifact ->
def id = artifact.moduleVersion.id
def coords = "${id.group}:${id.name}:${id.version}"
def file = artifact.file
if (file.name.endsWith('.aar') || file.name.endsWith('.jar')) {
println "发现AAR/JAR: $coords → ${file.name}"
aarFiles[coords] = file
}
}
// 3. 复制所有需要的AAR文件(包括手动指定的冲突依赖)
def copiedCount = 0
// 3.1 先复制手动指定的依赖(确保冲突的runtime被提取)
dependenciesToExtract.each { dependency ->
def parts = dependency.split(':')
def group = parts[0]
def name = parts[1]
def version = parts[2]
// 尝试从aarFiles中找到精确匹配的依赖
def matchingKey = aarFiles.keySet().find { it == dependency }
if (matchingKey) {
def sourceFile = aarFiles[matchingKey]
def targetFileName = "${group.replace('.', '-')}-${name}-${version}-${sourceFile.name}"
def targetFile = new File(destinationDir, targetFileName)
ant.copy(file: sourceFile, tofile: targetFile, overwrite: true)
println " ✓ 已复制: $dependency → ${targetFileName}"
copiedCount++
} else {
println " ✗ 未找到依赖: $dependency"
// 尝试从Gradle缓存中直接查找(备选方案)
def cacheDir = file("${System.getProperty('user.home')}/.gradle/caches/modules-2/files-2.1")
def groupDir = new File(cacheDir, "${group}/$name/$version")
if (groupDir.exists()) {
def found = false
groupDir.eachFile { hashDir ->
if (hashDir.isDirectory() && !found) {
hashDir.eachFile { file ->
if ((file.name.endsWith('.aar') || (file.name.endsWith('.jar') && !file.name.endsWith('sources.jar'))) && !found) {
def targetFileName = "${group.replace('.', '-')}-${name}-${version}-${file.name}"
def targetFile = new File(destinationDir, targetFileName)
ant.copy(file: file, tofile: targetFile, overwrite: true)
println " ✓ 从缓存中找到并复制: $dependency → ${targetFileName}"
copiedCount++
found = true
}
}
}
}
if (!found) {
println " ✗ 在缓存中也未找到AAR: $dependency"
}
} else {
println " ✗ 缓存目录不存在: $groupDir"
}
}
}
// 3.2 复制所有其他AAR(避免遗漏)
aarFiles.each { coords, file ->
// 跳过已手动复制的依赖
if (!dependenciesToExtract.contains(coords)) {
def parts = coords.split(':')
def group = parts[0]
def name = parts[1]
def version = parts[2]
def targetFileName = "${group.replace('.', '-')}-${name}-${version}-${file.name}"
def targetFile = new File(destinationDir, targetFileName)
ant.copy(file: file, tofile: targetFile, overwrite: true)
println " ✓ 已复制其他AAR/JAR: $coords → ${targetFileName}"
copiedCount++
}
}
println "\n===== 提取完成 ====="
println "提取的AAR文件已保存到: ${destinationDir}"
println "目标依赖总数: ${dependenciesToExtract.size()}"
println "成功提取的AAR数量: ${copiedCount}"
println "提取的AAR列表:"
// 打印最终提取的AAR列表
fileTree(destinationDir).files.each { file ->
println " - ${file.name}"
}
}
}
}
-
在模块
build.gradle中同时声明依赖:dependencies { implementation 'com.squareup.okio:okio:2.5.0' // 正常编译使用 retrieveAar 'com.squareup.okio:okio:2.5.0' // 供 copyAars 任务提取 } -
执行命令:
./gradlew copyAllAars
✅ 原理说明:
implementation用于编译和运行时依赖;retrieveAar是独立配置,不参与编译,仅作为copyAllAars任务的输入源;- Gradle 会自动从缓存(
~/.gradle/caches/modules-2/)中解析该 AAR 并复制。
替代方式:手动定位缓存
- 在 Android Studio 的 External Libraries 中展开依赖;
- 右键查看声明,即可看到其物理路径(通常为
~/.gradle/caches/transforms-2/...)。
三、构建缓存机制:加速增量编译
为提升构建速度,Gradle 采用多级缓存策略:
-
依赖元数据缓存:
~/.gradle/caches/modules-2/—— 存放 JAR/AAR 原始文件; -
Transform 缓存:
~/.gradle/caches/transforms-2/files-2.1/—— 存放解压后的 AAR(含classes.jar、res/、AndroidManifest.xml),供资源合并使用; -
构建输出缓存:
~/.gradle/caches/build-cache-1/—— 以哈希命名的压缩包,缓存 Task 输出(如编译后的.class、合并后的资源),支持跨项目复用。
🔁 缓存失效场景:
修改源码、依赖版本、构建脚本或清理build/目录,都会触发对应缓存失效,确保构建结果正确。
四、编译期字节码插桩(AOP):无侵入式代码增强
AOP(面向切面编程)允许在不修改源码的前提下,在编译期注入通用逻辑(如埋点、性能监控、权限校验)。
1. 核心概念(保持原始定义)
- Pointcut(切入点):指定在哪些方法/类上插入逻辑;
-
Advice(通知):要插入的具体代码,类型包括:
-
before(前置) -
after(后置) -
around(环绕)
-
- JointPoint(连接点):程序执行中可被拦截的点(如方法调用);
- Weaving(织入):将 Advice 应用到 JointPoint 的过程。
2. Android 中的插桩入口:Transform API
-
机制:通过 Transform API,可在
javac生成.class后、dex转换前修改字节码; -
注册方式:在自定义 Plugin 中,通过
AppExtension.registerTransform()注册; -
执行时机:在
java compile Task完成后,自动触发 Transform 类型的 Task; - 开发辅助:使用 ASM ByteCode Viewer(IDEA 插件)可直观查看目标方法的字节码,便于编写 ASM 指令。
✅ 典型流程:
源码 →javac→.class→ Transform(ASM/Javassist 修改) →.class(增强后)→dexBuilder→.dex
3. 主流 AOP 工具对比(完整保留原始描述)
| 工具 | 类型 | 输入 | 输出 | 特点 |
|---|---|---|---|---|
| APT(Annotation Processing Tool) | 源码生成 | 注解 + 源码 | 新的 .java 文件 |
构建代码,帮助你写任何不想重复写的代码;如 ButterKnife、Dagger 自动生成绑定代码 |
| AspectJ | 字节码注入 |
.java / .class
|
修改后的 .class
|
代码注入,将 Advice 和 PointCut 组合成 Aspect;支持 before/after/around;需额外编译器(ajc) |
| JavaPoet | 源码生成辅助库 | API 调用 |
.java 文件字符串 |
JavaFile 是对 .java 文件的抽象;TypeSpec 表示类;MethodSpec 表示方法;常与 APT 配合使用 |
| Javassist | 字节码操作 |
.class 文件 |
修改后的 .class
|
修改和创建代码;直接修改 class 文件;API 比 ASM 更友好,适合快速原型 |
| ASM | 字节码操作 |
.class 字节码 |
修改后的字节码 | 编译字节码的工具;性能最高,控制最精细,但需理解 JVM 指令集 |
💡 选型建议:
- 需要生成新类(如 Builder、Proxy)→ APT + JavaPoet;
- 需要修改现有方法逻辑(如加日志、统计耗时)→ Transform + ASM/Javassist;
- 追求极致性能与控制 → ASM;
- 快速验证想法 → Javassist。

五、可视化与参考资料
为辅助理解,原文包含多张关键流程图(读者可结合以下描述查阅原图):
-
Sync 流程图:展示 IDE → Gradle Daemon → Plugin.apply() 的交互;
Android Studio 视角
Gradle 视角 -
AGP 打包流程图:从源码/资源输入到 APK 输出的全链路;
打包过程 -
字节码插桩位置图:明确 Transform 在
javac与dex之间的位置。
编译打包总体流程
详细流程
延伸阅读(完整保留所有原始链接)
- Android Studio插件开发
- 如何创建Gradle插件开发
- 自定义Gradle 插件及遇到的问题
- 自定义 Gradle Plugin
- java2smalis
- 简析Gradle流程,Ant、Maven、Gradle区别
- 深入理解 Android Studio Sync 流程
- 依赖实现分析
- artifacts的发布
- Android Apk 编译打包流程
- build.gradle是怎么运行的
- Transform编织插入字节码
- APT,AspectJ,Javassist
- AspectJ的使用
- 编译插桩-AspectJ应用
- JavaPoet
- ASM Tree api
- APK构建原理由浅入深
结语:
Android 构建系统是一个“可观察、可干预、可扩展”的工程平台。从 Sync 触发 Plugin,到 Task 流水线执行,再到字节码层面的精细操控,每一步都为开发者提供了优化空间。




