Gradle在配置阶段提前解析依赖是设计使然,为构建完整依赖图以判断任务可执行性与插件就绪状态;但会引发网络请求、仓库校验等副作用,导致Sync卡顿、CI变慢或离线失败。

为什么配置阶段会提前解析依赖
Gradle 默认在配置阶段(configuration phase)就尝试解析所有 dependencies 块里的声明,哪怕你只运行 ./gradlew clean 或 ./gradlew tasks。这不是 bug,而是设计使然:Gradle 需要提前构建完整的依赖图,才能判断哪些任务可执行、哪些插件已就绪。但副作用也很明显——网络请求、仓库校验、POM 下载全会在你还没真正想编译时就触发,导致 Sync 卡顿、CI 构建变慢、离线环境直接失败。
用 afterEvaluate 延迟到执行阶段
最直接的规避方式是把依赖声明从顶层 dependencies 块中移出,改用 afterEvaluate 动态注入。这能确保依赖只在明确需要时(比如执行 compileJava)才开始解析。
示例:
afterEvaluate {
dependencies {
implementation 'com.google.guava:guava:31.1-jre'
}
}
注意:afterEvaluate 不是万能解药,它仅适用于你**确定该模块一定需要这些依赖**,且不介意失去 IDE 的实时类路径提示(如 Android Studio 可能标红未识别的类)。常见适用场景包括:条件化启用某套 SDK、CI 环境专用依赖、或临时绕过本地仓库不可用问题。
改用 configurations + resolutionStrategy 按需控制
更推荐的做法是保留声明位置,但切断“立即解析”链条。Gradle 允许你对配置(configuration)本身做延迟干预:
-
configurations.all { resolutionStrategy { cacheChangingModulesFor 0, 'seconds' } }:禁用变更模块缓存,避免反复检查远程更新 -
configurations.compileClasspath.setCanBeResolved(false):显式禁止该配置在配置阶段被解析(适用于仅需编译期 classpath 推导、不真下载的场景) -
configurations.runtimeClasspath.attributes.attribute(Usage.USAGE_ATTRIBUTE, objects.named(Usage, Usage.JAVA_RUNTIME)):用属性标记替代硬解析,把决策推后到执行阶段
这类操作不会改变依赖语义,但能显著减少配置阶段的 I/O 和网络开销。
真正要警惕的是隐式触发解析的操作
很多你以为“只是读个值”的行为,其实在悄悄拉取依赖:
- 在
build.gradle顶层调用configurations.compileClasspath.files—— 立刻触发解析 - 用
project.dependencies.create(...)创建依赖对象后,立刻传给sourceSets.main.java.srcDirs类似的 setter —— 触发即时解析 - 在
task.doFirst {}里写configurations.testRuntimeClasspath.asPath—— 执行阶段才查,安全;但如果写成task { outputs.dir layout.buildDirectory.dir('tmp').get() }并在里面引用configurations,仍属配置阶段
核心判断标准只有一个:代码是否出现在 task { ... } 大括号内、且不在 doFirst/doLast 闭包里。只要在这之外,就默认属于配置阶段,慎碰 configurations.*.files 或 *.asPath 这类 getter。











