在 Android 開發(fā)中,構(gòu)建系統(tǒng)是連接源代碼與最終 APK 的“隱形引擎”。它不僅負(fù)責(zé)編譯、打包,還支持高度定制化的擴展能力——從提取遠程依賴到編譯期代碼注入。然而,由于其模塊化設(shè)計和多階段流程,初學(xué)者常感碎片化、難成體系。

一、整體構(gòu)建流程:從 Sync 到 APK 生成
理解 Android 構(gòu)建的第一步,是把握其宏觀執(zhí)行路徑。
1. Sync 階段:項目配置加載
當(dāng)你在 build.gradle 中修改配置并點擊 「Sync Now」,Android Studio 會觸發(fā) Gradle 的 配置階段(Configuration Phase):
- Gradle 加載所有
build.gradle文件; - 執(zhí)行
apply plugin: 'com.android.application'; -
源碼中一定有一個
com.android.application.properties文件與之相對應(yīng),這便是 Plugin 的入口; - 最終調(diào)用
Plugin#apply()方法,注冊后續(xù)構(gòu)建任務(wù)。
? 關(guān)鍵點:Sync 不執(zhí)行編譯,只解析配置、注冊 Task。這是 AGP(Android Gradle Plugin)介入的起點。
2. 構(gòu)建執(zhí)行階段:Task 流水線
執(zhí)行 ./gradlew assembleDebug 時,AGP 按照依賴關(guān)系依次執(zhí)行一系列 Task,形成一條清晰的流水線:
| 階段 | 核心 Task | 作用說明 |
|---|---|---|
| 資源預(yù)處理 | :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,打包所有已編譯資源 |
| 字節(jié)碼處理 | 自定義 Transform(如有) | 在 .class 轉(zhuǎn) .dex 前插入字節(jié)碼修改邏輯(見第四部分) |
| DEX 轉(zhuǎn)換 | :app:dexBuilderDebug |
將 .class 轉(zhuǎn)換為 Dalvik 可執(zhí)行的 .dex 文件 |
| APK 打包 | :app:packageDebug |
將 .dex、資源、AndroidManifest.xml、assets、libs/(含 .so)打包為未簽名 APK |
(Release 版本額外執(zhí)行 zipalign 對齊與簽名) |
Debug 版本通常不啟用代碼混淆與資源壓縮,以加快開發(fā)迭代速度。
(2)Release 構(gòu)建(assembleRelease)完整 Task 流程 ?【新增完整列表】
Release 構(gòu)建在 Debug 基礎(chǔ)上增加了優(yōu)化、混淆、對齊、簽名等關(guān)鍵步驟,確保 APK 體積小、性能高、安全性強。以下是 assembleRelease 執(zhí)行的完整核心 Task 序列(按實際依賴順序排列):
| Task | 作用說明 |
|---|---|
:app:preBuild |
初始化構(gòu)建環(huán)境 |
:app:preReleaseBuild |
Release 構(gòu)建前準(zhǔn)備 |
: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 |
生成著色器資產(chǎn) |
:app:mergeReleaseAssets |
合并 assets/ 目錄 |
:app:compressReleaseAssets |
壓縮 assets |
:app:checkReleaseDuplicateClasses |
檢查重復(fù)類(防止依賴沖突) |
:app:mergeReleaseJniLibFolders |
合并 JNI 庫目錄 |
:app:mergeExtDexRelease |
合并擴展 DEX(Multidex 場景) |
:app:mergeProjectDexRelease |
合并主 DEX |
:app:optimizeReleaseResources |
資源優(yōu)化(若啟用 shrinkResources true) |
:app:mergeReleaseNativeLibs |
合并 .so 文件到 lib/ 目錄 |
:app:stripReleaseDebugSymbols |
移除 Native 庫中的調(diào)試符號(減小體積) |
:app:validateSigningRelease |
驗證簽名配置 |
:app:writeReleaseApplicationId |
寫入 Application ID |
:app:compileReleaseKotlin |
編譯 Kotlin 源碼 |
:app:compileReleaseJavaWithJavac |
編譯 Java 源碼 |
:app:transformReleaseClassesWithAsm(如有) |
自定義 ASM Transform 修改字節(jié)碼 |
:app:minifyReleaseWithR8 或 :app:shrinkReleaseRes
|
代碼混淆與資源縮減(若啟用 minifyEnabled true)? 使用 R8(默認(rèn))或 ProGuard? 移除未使用類、方法、字段? 混淆命名(a, b, c...) |
:app:processReleaseResources |
AAPT2 鏈接階段:生成 R.java 和 resources.arsc
|
:app:packageRelease |
打包未對齊、未簽名的 APK |
:app:signReleaseBundle / :app:signReleaseApk
|
使用 keystore 對 APK 簽名 |
:app:bundleReleaseResources(AAB) |
若構(gòu)建 AAB,則打包資源 bundle |
:app:collectReleaseDependencies |
收集依賴信息 |
:app:configureReleaseDependencies |
配置依賴 |
:app:mergeReleaseGeneratedProguardFiles |
合并 ProGuard 規(guī)則文件 |
:app:packageReleaseUniversalApk(如有) |
生成 universal APK(含所有 ABI) |
:app:assembleRelease |
最終聚合 Task,標(biāo)志著 Release 構(gòu)建完成 |
?? 關(guān)鍵優(yōu)化項說明:
minifyEnabled true:啟用 R8/ProGuard,大幅減小 APK 體積;shrinkResources true:移除未引用的資源(需配合minifyEnabled);zipAlign:在package后自動執(zhí)行(AGP 內(nèi)部集成),確保內(nèi)存對齊,提升運行效率;- 簽名:必須提供有效的
keystore配置,否則構(gòu)建失敗。
? 示例:為何 .so 文件丟失?
Native 庫(.so)在mergeReleaseNativeLibs階段被復(fù)制到./app/build/intermediates/merged_native_libs/release/out/lib/。
若該目錄為空或內(nèi)容異常,.so 丟失的大部分原因是由于合并出錯。
解決方案:刪除merged_native_libs/和~/.gradle/caches/build-cache-1/,強制重新執(zhí)行合并任務(wù)。
二、Gradle 插件與依賴管理:構(gòu)建系統(tǒng)的擴展能力
AGP 提供了標(biāo)準(zhǔn)流程,但真實項目常需定制行為——這正是 Gradle Plugin 的價值所在。
1. 自定義 Plugin:擴展構(gòu)建邏輯
通過編寫自定義 Plugin,可注冊新 Task、修改現(xiàn)有行為或注入 AOP 邏輯(見第四部分)。其核心是實現(xiàn) Plugin<Project> 接口,并在 apply() 中操作 Project 對象。
2. 實戰(zhàn):提取遠程 AAR 依賴為本地文件
場景:需要離線分發(fā)某第三方庫,或?qū)徲嬈鋬?nèi)容。
實現(xiàn)步驟:
-
在根項目
build.gradle中定義配置與任務(wù):
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'
]
// 創(chuàng)建任務(wù)強制解析所有依賴并提取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 = [:] // 格式:依賴坐標(biāo) -> 文件路徑
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 "發(fā)現(xiàn)AAR/JAR: $coords → ${file.name}"
aarFiles[coords] = file
}
}
// 3. 復(fù)制所有需要的AAR文件(包括手動指定的沖突依賴)
def copiedCount = 0
// 3.1 先復(fù)制手動指定的依賴(確保沖突的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 " ? 已復(fù)制: $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 " ? 從緩存中找到并復(fù)制: $dependency → ${targetFileName}"
copiedCount++
found = true
}
}
}
}
if (!found) {
println " ? 在緩存中也未找到AAR: $dependency"
}
} else {
println " ? 緩存目錄不存在: $groupDir"
}
}
}
// 3.2 復(fù)制所有其他AAR(避免遺漏)
aarFiles.each { coords, file ->
// 跳過已手動復(fù)制的依賴
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 " ? 已復(fù)制其他AAR/JAR: $coords → ${targetFileName}"
copiedCount++
}
}
println "\n===== 提取完成 ====="
println "提取的AAR文件已保存到: ${destinationDir}"
println "目標(biāo)依賴總數(shù): ${dependenciesToExtract.size()}"
println "成功提取的AAR數(shù)量: ${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 任務(wù)提取 } -
執(zhí)行命令:
./gradlew copyAllAars
? 原理說明:
implementation用于編譯和運行時依賴;retrieveAar是獨立配置,不參與編譯,僅作為copyAllAars任務(wù)的輸入源;- Gradle 會自動從緩存(
~/.gradle/caches/modules-2/)中解析該 AAR 并復(fù)制。
替代方式:手動定位緩存
- 在 Android Studio 的 External Libraries 中展開依賴;
- 右鍵查看聲明,即可看到其物理路徑(通常為
~/.gradle/caches/transforms-2/...)。
三、構(gòu)建緩存機制:加速增量編譯
為提升構(gòu)建速度,Gradle 采用多級緩存策略:
-
依賴元數(shù)據(jù)緩存:
~/.gradle/caches/modules-2/—— 存放 JAR/AAR 原始文件; -
Transform 緩存:
~/.gradle/caches/transforms-2/files-2.1/—— 存放解壓后的 AAR(含classes.jar、res/、AndroidManifest.xml),供資源合并使用; -
構(gòu)建輸出緩存:
~/.gradle/caches/build-cache-1/—— 以哈希命名的壓縮包,緩存 Task 輸出(如編譯后的.class、合并后的資源),支持跨項目復(fù)用。
?? 緩存失效場景:
修改源碼、依賴版本、構(gòu)建腳本或清理build/目錄,都會觸發(fā)對應(yīng)緩存失效,確保構(gòu)建結(jié)果正確。
四、編譯期字節(jié)碼插樁(AOP):無侵入式代碼增強
AOP(面向切面編程)允許在不修改源碼的前提下,在編譯期注入通用邏輯(如埋點、性能監(jiān)控、權(quán)限校驗)。
1. 核心概念(保持原始定義)
- Pointcut(切入點):指定在哪些方法/類上插入邏輯;
-
Advice(通知):要插入的具體代碼,類型包括:
-
before(前置) -
after(后置) -
around(環(huán)繞)
-
- JointPoint(連接點):程序執(zhí)行中可被攔截的點(如方法調(diào)用);
- Weaving(織入):將 Advice 應(yīng)用到 JointPoint 的過程。
2. Android 中的插樁入口:Transform API
-
機制:通過 Transform API,可在
javac生成.class后、dex轉(zhuǎn)換前修改字節(jié)碼; -
注冊方式:在自定義 Plugin 中,通過
AppExtension.registerTransform()注冊; -
執(zhí)行時機:在
java compile Task完成后,自動觸發(fā) Transform 類型的 Task; - 開發(fā)輔助:使用 ASM ByteCode Viewer(IDEA 插件)可直觀查看目標(biāo)方法的字節(jié)碼,便于編寫 ASM 指令。
? 典型流程:
源碼 →javac→.class→ Transform(ASM/Javassist 修改) →.class(增強后)→dexBuilder→.dex
3. 主流 AOP 工具對比(完整保留原始描述)
| 工具 | 類型 | 輸入 | 輸出 | 特點 |
|---|---|---|---|---|
| APT(Annotation Processing Tool) | 源碼生成 | 注解 + 源碼 | 新的 .java 文件 |
構(gòu)建代碼,幫助你寫任何不想重復(fù)寫的代碼;如 ButterKnife、Dagger 自動生成綁定代碼 |
| AspectJ | 字節(jié)碼注入 |
.java / .class
|
修改后的 .class
|
代碼注入,將 Advice 和 PointCut 組合成 Aspect;支持 before/after/around;需額外編譯器(ajc) |
| JavaPoet | 源碼生成輔助庫 | API 調(diào)用 |
.java 文件字符串 |
JavaFile 是對 .java 文件的抽象;TypeSpec 表示類;MethodSpec 表示方法;常與 APT 配合使用 |
| Javassist | 字節(jié)碼操作 |
.class 文件 |
修改后的 .class
|
修改和創(chuàng)建代碼;直接修改 class 文件;API 比 ASM 更友好,適合快速原型 |
| ASM | 字節(jié)碼操作 |
.class 字節(jié)碼 |
修改后的字節(jié)碼 | 編譯字節(jié)碼的工具;性能最高,控制最精細,但需理解 JVM 指令集 |
?? 選型建議:
- 需要生成新類(如 Builder、Proxy)→ APT + JavaPoet;
- 需要修改現(xiàn)有方法邏輯(如加日志、統(tǒng)計耗時)→ Transform + ASM/Javassist;
- 追求極致性能與控制 → ASM;
- 快速驗證想法 → Javassist。

五、可視化與參考資料
為輔助理解,原文包含多張關(guān)鍵流程圖(讀者可結(jié)合以下描述查閱原圖):
-
Sync 流程圖:展示 IDE → Gradle Daemon → Plugin.apply() 的交互;
Android Studio 視角
Gradle 視角 -
AGP 打包流程圖:從源碼/資源輸入到 APK 輸出的全鏈路;
打包過程 -
字節(jié)碼插樁位置圖:明確 Transform 在
javac與dex之間的位置。
編譯打包總體流程
詳細流程
延伸閱讀(完整保留所有原始鏈接)
- Android Studio插件開發(fā)
- 如何創(chuàng)建Gradle插件開發(fā)
- 自定義Gradle 插件及遇到的問題
- 自定義 Gradle Plugin
- java2smalis
- 簡析Gradle流程,Ant、Maven、Gradle區(qū)別
- 深入理解 Android Studio Sync 流程
- 依賴實現(xiàn)分析
- artifacts的發(fā)布
- Android Apk 編譯打包流程
- build.gradle是怎么運行的
- Transform編織插入字節(jié)碼
- APT,AspectJ,Javassist
- AspectJ的使用
- 編譯插樁-AspectJ應(yīng)用
- JavaPoet
- ASM Tree api
- APK構(gòu)建原理由淺入深
結(jié)語:
Android 構(gòu)建系統(tǒng)是一個“可觀察、可干預(yù)、可擴展”的工程平臺。從 Sync 觸發(fā) Plugin,到 Task 流水線執(zhí)行,再到字節(jié)碼層面的精細操控,每一步都為開發(fā)者提供了優(yōu)化空間。




