构建阶段与生命周期:配置、依赖解析、编译、打包、测试
说到AGP版本升级,很多朋友第一反应就是改个com.android.tools.build:gradle版本号。但我跟你说,真正的坑往往藏在构建管道的每个拐角处。我从Gradle 3.0一直摸到现在的8.x,每次版本跳跃,那些所谓“内部优化”都会悄悄改变你项目里某个阶段的网络行为。
这一章,咱们就来拆解构建的五个核心阶段。搞清楚每个阶段在做什么,网络请求发生在哪里,你才能看懂后面的瀑布图。
阶段一:配置(Configuration)
我个人喜欢把配置阶段比作建筑师画设计图。Gradle先读取你所有的.gradle文件,解析buildscript、allprojects、subprojects里的闭包。这时候,Gradle会创建Project和Task对象,但还不执行任何实际动作。
网络请求在这个阶段主要出现在两处:
- 插件仓库解析:Gradle要从你声明的仓库(比如google()、mavenCentral())下载AGP插件本身。我遇到过一个大坑——公司内网代理设置不对,结果配置阶段卡在下载
com.android.tools.build:gradle:8.2.0的pom文件上,一卡就是三分钟。 - 依赖坐标声明:虽然只是声明,但如果你用了
dependencies { implementation 'com.example:lib:1.+' }这种动态版本,配置阶段就会联网去查最新版本号。嗯,这里要注意:新版AGP的依赖解析引擎改用了更激进的缓存策略,动态版本查询频率提高了,网络瀑布图上会多出一小簇请求。
升级后的变化:从AGP 7.0开始,配置阶段默认启用了配置缓存(Configuration Cache)。如果项目没配合好,比如用了自定义buildscript闭包里动态生成task,配置缓存会失效,网络请求就会在每次构建时重复执行——瀑布图上出现一模一样的请求片段。
dependencyResolutionManagement统一管理仓库。
阶段二:依赖解析(Dependency Resolution)
配置阶段只是“说”我要谁,依赖解析阶段才是真正“找”谁。Gradle会解析所有依赖的版本冲突、传递依赖,并下载jar/aar到本地缓存。你想想看,这个阶段是网络瀑布图的主战场。
核心流程三步走:
- 解析元数据:下载每个依赖的
.pom或.module文件,读取依赖树。 - 冲突解决:AGP会按照
force、strictly、require等规则帮你选版。老版本如果遇到冲突,干脆抛异常;新版本会尝试自动降级,但可能会多拉一个意想不到的版本——网络瀑布图上偶尔多出一段独立的分支。 - 下载构件:真正下载aar/jar。注意,这里有一个关键差异:AGP 4.0以前,依赖解析是纯串行的——一个一个库按顺序下载。从7.0开始,Gradle引入了并行依赖解析,多个库的元数据可以同时拉取。瀑布图上就会看到原本一个长条变成几个叠加的小条。
| AGP版本 | 依赖解析模式 | 网络瀑布图特征 |
|---|---|---|
| 3.x ~ 4.x | 串行 | 一个依赖接一个依赖,长条+长串 |
| 5.x ~ 6.x | 部分并行(元数据+构件分开) | 前半段密集短条,后半段稀疏长条 |
| 7.0+ | 全并行 + 智能缓存 | 极短时间密集爆发,然后迅速收敛 |
我在项目中遇到过的情况是:升级到AGP 8.0后,依赖解析阶段的总时长从原来的40秒降到12秒,但网络瀑布图上同时发起的连接数暴增到35个,把公司内网的Nginx直接打挂了。后来给CI加了并发限流才稳住。
--max-workers=4降低并行度。
阶段三:编译(Compilation)
编译阶段是CPU和内存的战场,但网络请求依然会意外乱入。为什么会这样?我举两个常见的场景:
- 注解处理器(APT / KAPT):比如你用
Room、Dagger、Glide,这些处理器在编译时会生成代码。如果处理器本身依赖了某些工具库(比如com.squareup:javapoet),而且你在kapt配置里用了动态版本,就会触发二次依赖解析。 - 增量编译的元数据同步:从AGP 4.2开始,增量编译会记录每个Java文件的上次编译状态。如果开启了
android.enableR8.fullMode=true,有时候R8会从远程拉取一些“程序数据库”(多语言字符串等,看具体配置)。
升级后最容易踩的坑是KAPT → KSP的迁移。我用很多项目从AGP 7.x升级到8.x时,KSP的依赖下载机制不一样了——老版KAPT是把注解处理器当作编译类路径的一部分直接下,新版KSP需要单独声明ksp配置,网络瀑布图上会多出一条“ksp-classpath”的请求线。
阶段四:打包(Packaging)
打包阶段就是把所有DEX、资源、原库文件捏成一个APK。这里有两个网络参与的点:
- R8/ProGuard的映射文件下载:如果你在混淆配置里用了
-libraryjars指向远程仓库,或者依赖了某些只提供混淆后的SDK(比如微信支付、支付宝老版本),打包阶段会去下载这些.jar。 - 应用签名服务:现在很多团队用Play App Signing或者第三方远程签名服务,打包阶段需要上传未签名的APK并下载签名后的APK。这条网络请求在瀑布图上非常显眼,因为它发生在打包阶段的最后。
说实话,我在AGP 7.3项目中遇到过打包阶段突然多了几百KB的下载量——后来发现是因为新版本把android:extractNativeLibs="false"这个属性改成了默认未压缩,但实际还是读取了远程的native库元数据来校验。这种隐性流量在旧版本根本没有。
阶段五:测试(Testing)
测试阶段,尤其是仪器化测试(Instrumented Test)的网络行为最容易忽略。当你跑./gradlew connectedCheck时,AGP会:
- 下载或更新测试用的Android系统镜像(如果设备是模拟器,且你开了自动更新)。
- 上传APK和测试APK到设备/模拟器。
- 测试框架(如Espresso)本身有依赖:如果项目里用了
androidx.test:runner,可能会触发子依赖的元数据重解析。
升级后要注意:从AGP 7.2开始,测试任务默认启用并行执行(如果你有多个设备或模拟器)。网络瀑布图上会看到多条“测试APK上传”的短线重叠在一起。我曾经因为测试设备列表配置了4个模拟器,结果Kubernetes集群内的网络带宽吃满,导致整个CI挂掉——后来限制maxParallelForks=2才解决。
实践:如何定位瀑布图中的问题阶段
升级完AGP,拿到一张新的网络瀑布图,你怎么快速断定是哪个阶段出了问题?我总结了一个三步排查法:
- 看时间线头部:如果所有网络请求集中在构建启动后前10秒,且大量重复请求,查配置阶段的插件和缓存。
- 看请求量峰值:如果在某个时间段同时爆发30+个请求,看依赖解析阶段的并行下载量。去
~/.gradle/caches看一眼,如果缓存目录异常大,说明旧缓存没被清除。 - 看尾部异常:如果最后出现一个孤零零的大文件下载(比别的请求都长5倍),查打包或测试阶段的远程签名/镜像。
举个例子,我去年迁移一个20模块的项目到AGP 8.1,瀑布图在依赖解析阶段显示有38个请求连续并发,但实际我们只声明了12个外部依赖。后来发现是mavenCentral()和jcenter()两个仓库里各有一份同名依赖,Gradle串行走了两个仓库去验证——虽然新版支持并行,但冗余的仓库声明反而拖慢了速度。删掉jcenter()后请求数降到12个,时间缩短60%。
./gradlew build --info跑一次,看日志里每个阶段的耗时分布。比对着瀑布图的时间轴,你就能知道每个时间段正在执行哪个阶段的任务。我习惯把这两个时间线叠在一起分析——比单看任一个都直观得多。