新装的开发机上,第一次点开 Android Studio 的 Sync,进度条卡在Downloading https://services.gradle.org/distributions/gradle-8.7-bin.zip一动不动,十分钟后弹出一行java.net.SocketTimeoutException,这是很多人接触 Gradle 的第一课。问题不在于 Gradle 本身难用,而在于它的发行版分发包默认走的是海外单一源,几十到两百多兆的压缩包一旦中途断流,整次构建就直接归零,而 IDE 又不会告诉你它到底下到了哪一步、文件落在了哪儿。
这篇内容把 Gradle 各版本的下载地址规律、公共镜像入口、手工放置离线包的目录技巧,以及四类高频报错的排查链路一次讲清楚。不管你是刚装完 Android Studio 的新手,还是在公司内网里要给整组人配一套构建环境的老手,都能从里面挑到能直接抄的部分。核心会用到的关键词包括 gradle 各版本下载、gradle 离线包、gradle 公共镜像、gradle 安装配置,以及 wrapper 目录的手工干预方式。
1. 从一次卡住的 Sync 说起:Gradle 下载为什么会成为第一道门槛
1.1 Wrapper 到底在什么时刻触发下载
Gradle 有两种存在形式:一种是你在系统里装好的那个gradle命令,另一种是项目自带的 Gradle Wrapper(项目根目录下的gradlew、gradlew.bat和gradle/wrapper/gradle-wrapper.properties)。现代项目几乎全部用后者,因为 Wrapper 把"用哪个 Gradle 版本构建"这件事写进了代码库,谁拉下来代码都一样,不会出现"你本地是 7.6 我本地是 8.7 所以构建结果不同"的扯皮。
真正触发下载的时机有两个,很多人只注意到第一个:
- 第一次执行
./gradlew或者 IDE 点 Sync 时,Wrapper 会读取gradle-wrapper.properties里的distributionUrl,然后去~/.gradle/wrapper/dists/下面找有没有对应的发行版; - 找不到,就开始下载、校验、解压,整个过程在
dists目录里留下痕迹;找到就直接复用,不再联网。
所以"下载慢"这件事,本质上是第一次冷启动的成本。理解了这一点,后面所有的加速手段其实都围绕一个目标:让这台机器、或者整个团队的所有机器,尽量只下载一次,或者从更近的地方下载。
注意:
~在 Windows 上对应C:\Users\你的用户名。Gradle 的用户目录默认是C:\Users\你的用户名\.gradle,这个目录会随着你切换项目、切换版本不断变大,几个 GB 是常态。
1.2 bin、all、src 三种包体,别下错也别下多
同一个 Gradle 版本,官方会放出好几种压缩包,名字只差一个后缀,体积差得却不少。选错了包体,是"下载特别慢"的一个常见但容易被忽略的原因。
| 包体后缀 | 内容 | 体积量级 | 适用场景 |
|---|---|---|---|
-bin.zip | 可执行发行版,含 Gradle 二进制与核心运行时 | 约 100–130 MB | 绝大多数日常开发、CI 构建 |
-all.zip | 在 bin 基础上加了源码和文档 | 约 200 MB+ | 需要在 IDE 里跳进 Gradle 源码调试、写插件 |
-src.zip | 只有源码 | 约 30–40 MB | 纯粹阅读源码,不能直接执行 |
默认生成的 Wrapper 配置用的是-bin.zip,这是对的,别手贱改成-all。我见过有同事为了让 IDE 的代码跳转更顺,把distributionUrl里的bin换成all,结果每台机器都要多下七十多兆,在内网共享盘上还好,走公网就是纯纯的自找麻烦。真要看 Gradle 源码,本地解开一个-all包挂在 IDE 里做 source attachment 更划算。
1.3 AGP 与 Gradle 的版本绑定,决定了你不能随便挑版本
Android 项目里还有一个绕不开的约束:Android Gradle Plugin(AGP)对 Gradle 的最低版本有硬性要求。你在distributionUrl里随手写个低版本,Sync 直接报"Minimum supported Gradle version is x.y"。
下面这张表是常见组合,可以作为选版本时的起点。它不是官方矩阵的完整复制,具体以你项目里 AGP 官方文档标注的最低版本为准。
| AGP 版本 | 要求的最低 Gradle 版本 |
|---|---|
| 8.7.x | 8.9 |
| 8.6.x | 8.7 |
| 8.5.x | 8.7 |
| 8.4.x | 8.6 |
| 8.3.x | 8.4 |
| 8.2.x | 8.2 |
| 8.1.x | 8.0 |
| 8.0.x | 8.0 |
| 7.4.x | 7.5 |
这里有个实操上的取舍:不要一味追新,也不要一直苟在旧版本。追新意味着你的机器要再下一次新包,苟旧意味着迟早有一天某个三方库的 AGP 要求把你顶下去。我的习惯是,在一个项目周期内锁定一个 Gradle 版本不再动,等版本升级窗口来了再整体抬一次,同时顺手把dists目录清理一遍。
2. 把下载地址拆开看:Gradle 各版本发布物的命名规律与归档位置
2.1 官方分发路径的固定格式
Gradle 的发行版分发路径格式非常规整,掌握这个规律之后,你可以凭版本号直接拼出地址,不用去官网页面翻半天:
https://services.gradle.org/distributions/gradle-<版本号>-<包体>.zip举几个具体的例子,方便你对照:
https://services.gradle.org/distributions/gradle-8.7-bin.zip https://services.gradle.org/distributions/gradle-8.7-all.zip https://services.gradle.org/distributions/gradle-7.6.4-bin.zip https://services.gradle.org/distributions/gradle-6.9.4-bin.zipdistributionUrl里写地址时,记得反斜杠转义规则:在.properties文件里,冒号后面通常写成https\://,这是历史遗留的转义写法,Gradle 本身也接受不转义的写法,但为了跟 IDE 自动生成的保持一致,建议保留https\://的形式。
2.2 版本号后缀里的 rc、milestone 与 nightly
除了正式版,Gradle 还有几类预发布版本,命名后缀不一样,下载路径也随之变化:
- 候选发布版:
gradle-8.8-rc-1-bin.zip,路径中rc-1是连字符连接; - 里程碑版:
gradle-8.8-milestone-1-bin.zip,早期的大版本特性预览; - 每夜构建:路径前缀会变到
distributions-snapshots下面,且文件名里带时间戳,例如gradle-8.9-20240915.123456-1-bin.zip。
这里有个很实际的坑:公共镜像站通常只同步正式版,不同步 rc、milestone 和每夜构建。如果你把distributionUrl改成了镜像地址,又恰好用了 rc 版,就会 404。所以改镜像的前提是,你用的是正式发布版本。
2.3 校验文件与离线校验的完整做法
官方对每个发行包都提供了对应的.sha256文件,路径就是原地址后面加.sha256。这个文件在排查"下载下来的包到底完不完整"时非常有用,因为很多"Could not install Gradle distribution"的根因就是压缩包被截断了,而报错信息并不会直白地告诉你这一点。
Linux / macOS 下的校验流程:
# 下载包与校验文件 curl -O https://services.gradle.org/distributions/gradle-8.7-bin.zip curl -O https://services.gradle.org/distributions/gradle-8.7-bin.zip.sha256 # 校验(sha256 文件内容形如 "<hash> gradle-8.7-bin.zip") sha256sum -c gradle-8.7-bin.zip.sha256Windows 下没有sha256sum,用系统自带的certutil顶一下:
certutil -hashfile gradle-8.7-bin.zip SHA256输出的哈希值跟.sha256文件里的那一串比对,一致就说明包是完好的。这一步看着多余,但在内网文件服务器分发离线包的场景里,它能帮你省掉大量"为什么别人能用我不能用"的扯皮。
注意:
.sha256文件里通常包含文件名,sha256sum -c会按那个文件名去找文件。如果你把 zip 改了名字,校验会失败,此时手动比对哈希值即可。
3. 把速度真正拉起来:镜像源、内网文件服务与 dists 目录的三条路
3.1 改 distributionUrl 指向公共镜像站
最直接的做法,是把gradle-wrapper.properties里的distributionUrl换成公共镜像站上的同版本文件。常见的云厂商镜像入口大致有这几个(目录内容以镜像站页面实际为准,用之前务必先验证):
https://mirrors.cloud.tencent.com/gradle/ https://mirrors.huaweicloud.com/gradle/ https://mirrors.aliyun.com/macports/distfiles/gradle/改造后的gradle-wrapper.properties大概长这样:
distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists distributionUrl=https\://mirrors.cloud.tencent.com/gradle/gradle-8.7-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists改造前先验证地址是否真的存在,一条命令就够:
curl -I https://mirrors.cloud.tencent.com/gradle/gradle-8.7-bin.zip看到HTTP/1.1 200 OK并且Content-Length在百兆量级,才说明这个版本的包确实在。如果返回 404,说明该镜像没有收录这个版本,换一个镜像入口再试。
这里有一个真实存在的坑,值得单独拎出来:Gradle 在dists目录下存放发行版时,子目录名是由distributionUrl的哈希值算出来的。也就是说,你把地址从官方域名改到镜像域名,哈希值随之改变,Gradle 会认为这是一个全新的发行版,之前已经下好的那份缓存完全不会被复用,一切从头再来。所以在团队协作里,要么统一都用镜像地址,要么统一都用官方地址,来回横跳的代价就是重复下载。
3.2 手工下载 + file:/// 指向内网文件服务
如果你们有一个内网文件服务器或者共享盘,那就把"下载"这件事变成"拷文件"。做法是:
- 在某台能正常下载的机器上,把
gradle-8.7-bin.zip下好、校验通过; - 放到共享目录,比如
\\fileserver\build-tools\gradle\gradle-8.7-bin.zip; - 把项目里的
distributionUrl改成file://形式。
distributionUrl=file\:///D:/build-tools/gradle/gradle-8.7-bin.zipWindows 网络路径的写法要注意,UNC 路径需要转成file://形式:
distributionUrl=file\://fileserver/build-tools/gradle/gradle-8.7-bin.zip这条路是我在离线开发环境里用得最多的。它的好处是下载速度取决于局域网带宽,几百兆的包几秒钟就到位,而且完全不依赖外部网络状况。坏处是distributionUrl里带上了具体的机器路径,直接提交到代码库会让别人的构建挂掉,所以更适合放在本机gradle.properties或者 CI 配置里覆盖,而不是提交到仓库。
3.3 直接往 dists 目录里放一个已经解压好的发行版
这是最"暴力"也最有效的办法,适用于你不想动distributionUrl、也不想让 Gradle 再去解压的场景。
~/.gradle/wrapper/dists/下面的目录结构是这样的:
~/.gradle/wrapper/dists/ └── gradle-8.7-bin/ └── <一串由 URL 哈希生成的目录名>/ ├── gradle-8.7-bin.zip ├── gradle-8.7-bin.zip.ok └── gradle-8.7/ ├── bin/ ├── lib/ └── ...哈希目录名不好手算,但有个取巧的办法:让 Gradle 自己先建好这个目录。哪怕下载失败了,目录也会被创建出来,只是里面是空的或者只有一个残缺的 zip。这时候你把手工下好的完整 zip 放进去,再执行一次构建,Gradle 会自己对 zip 做校验并解压,生成.ok标记文件。整个过程不需要你去猜哈希值,也不需要手动解压。
实践中有两个细节要注意:
.ok文件是关键标记。只有 zip 没有.ok,Gradle 会重新校验并解压,这是正常的;但如果 zip 本身损坏,它会重新下载。所以放进去之前先做一次哈希校验,别把半截包扔进去。- 改了
distributionUrl之后哈希目录会变。这就是为什么用镜像地址的团队,dists目录下的子目录名和用官方地址的团队不一样。跨团队拷缓存时,最好连目录名一起拷,或者干脆统一地址。
3.4 别忘了依赖仓库也要换,init.gradle一把梭
很多人把发行版下载地址换成了镜像,结果 Sync 还是慢,原因是卡在依赖解析阶段。发行版只是 Gradle 自身,真正的大头是那些.jar、.aar,它们从 Maven 仓库拉取,同样有镜像可用。
在~/.gradle/init.gradle里写一段全局仓库替换,对所有项目生效,这是我认为性价比最高的一个配置:
// ~/.gradle/init.gradle allprojects { buildscript { repositories { maven { url 'https://maven.aliyun.com/repository/public' } maven { url 'https://maven.aliyun.com/repository/google' } maven { url 'https://maven.aliyun.com/repository/gradle-plugin' } mavenCentral() } } repositories { maven { url 'https://maven.aliyun.com/repository/public' } maven { url 'https://maven.aliyun.com/repository/google' } mavenCentral() } }注意buildscript块和普通repositories块要分开写。前者管的是插件类依赖,后者管的是项目编译依赖,漏掉buildscript这一段,插件解析还是走原来的地址。华为云的 Maven 仓库https://repo.huaweicloud.com/repository/maven/也是可选项,可以和上面几个搭配着用,哪个快用哪个。
另外,init.gradle是全局生效的,如果你在公司项目里被要求使用内部私有仓库,注意别让它把私有仓库的顺序顶掉,必要的话在项目自己的settings.gradle里重新声明。
4. 装一次用一年:Windows 与 macOS 下手动安装 Gradle 的完整动作
4.1 解压位置与环境变量的取舍
虽然 Wrapper 已经能满足绝大多数场景,但在需要频繁写自定义任务、调试构建脚本、或者做多项目脚手架的时候,本机装一个 Gradle 命令行还是方便得多。
Windows 上的完整动作:
- 下载
gradle-8.7-bin.zip,用certutil校验哈希; - 解压到不含中文和空格的路径,比如
D:\dev\gradle-8.7(解压出来会有一层gradle-8.7目录,最终路径是D:\dev\gradle-8.7\gradle-8.7,这一点经常有人搞混); - 新建系统变量
GRADLE_HOME,值为D:\dev\gradle-8.7\gradle-8.7; - 编辑
Path,新增%GRADLE_HOME%\bin; - 重开一个终端,执行
gradle -v,能看到版本号和 JVM 信息就算成功。
macOS / Linux 上:
# 假设包已经在当前目录 unzip gradle-8.7-bin.zip -d /opt/gradle # 追加到 shell 配置 echo 'export PATH=/opt/gradle/gradle-8.7/bin:$PATH' >> ~/.zshrc source ~/.zshrc gradle -v/opt目录在某些系统上需要管理员权限,个人开发机更推荐放到~/dev/或~/.local/下面,避免权限问题。
4.2 GRADLE_USER_HOME:最该改却最少人改的变量
GRADLE_USER_HOME默认指向~/.gradle,它下面装着这些体积大户:
| 子目录 | 内容 | 体积增长原因 |
|---|---|---|
wrapper/dists | 各版本 Gradle 发行版 | 每个版本 100–200 MB |
caches/modules-2 | 下载的依赖 jar/aar | 长期累积,随项目数量线性增长 |
caches/build-cache-1 | 构建缓存产物 | 开启构建缓存后增长很快 |
daemon | 守护进程日志 | 不怎么占空间,但会堆很多文件 |
如果你的 C 盘是系统盘且容量紧张,把这个变量指到数据盘是很有必要的:
# 系统环境变量 GRADLE_USER_HOME=D:\gradle-home改完之后,之前下好的所有缓存都不会自动迁移,你需要手动把老目录里的wrapper、caches拷过去,或者干脆接受重新下载一次。改之前先想清楚,别在赶进度的当天下午动这个。
4.3 gradle.properties 里值得写的几行
用户目录下的~/.gradle/gradle.properties是全局配置,对所有项目生效,下面这几行是我认为值得长期保留的:
# JVM 参数,构建脚本和编译进程共用 org.gradle.jvmargs=-Xmx4g -XX:MaxMetaspaceSize=1g -Dfile.encoding=UTF-8 # 开启并行构建,多模块项目收益明显 org.gradle.parallel=true # 开启构建缓存,重复构建速度提升很大 org.gradle.caching=true # 下载超时,网络状况不佳时加大到 120 秒 systemProp.org.gradle.internal.http.connectionTimeout=120000 systemProp.org.gradle.internal.http.socketTimeout=120000org.gradle.jvmargs那一行的内存值要根据机器内存来定。8 GB 内存的机器给 4 GB 已经很激进了,同时开 IDE 和模拟器容易触发交换。我一般按机器内存的一半往下取整来设,16 GB 机器给 4g,32 GB 给 8g,比较稳妥。
后面两个超时参数是排查SocketTimeoutException时的第一把扳手,具体原理放到第 5 章讲。
4.4 IDEA / Android Studio 里的 Gradle 配置该怎么选
IDE 里 Settings → Build, Execution, Deployment → Build Tools → Gradle 这一页,有两处关键设置:
- Use Gradle from:可以选
gradle-wrapper.properties file或者Specified location。前者跟随项目 Wrapper(推荐,团队一致性最好),后者指向你本机装的那个 Gradle(适合做插件开发或者想复用已下好的本地发行版)。 - Gradle JVM:这里选哪个 JDK,直接决定了构建用哪个 Java 版本。如果这一项和命令行里
java -version不一致,会出现"命令行能构建、IDE 里报错"的玄学现象。
一个我踩过的坑:IDE 里把 Gradle 切成了本地指定版本8.7,而项目 Wrapper 里写的是8.5,结果 CI 上跑出来一堆用了新 API 的报错。后来统一规矩——IDE 里也一律用 Wrapper,只有临时排查问题时才切本地版本。
5. 报错现场复盘:从 SocketTimeoutException 到 ModuleVersionResolveException 的排查链路
5.1 SocketTimeoutException:先分清是下载发行版还是解析依赖
java.net.SocketTimeoutException这个异常本身不区分场景,但报错栈里的上下文能区分。判断方法很简单,看异常前后打印的那行 URL:
- URL 是
services.gradle.org/distributions/...,说明卡在发行版下载; - URL 是某个 Maven 仓库地址,说明卡在依赖解析。
两种情况的处理手段不同。发行版下载的救急手段是加大超时和换镜像;依赖解析的救急手段是配init.gradle仓库镜像、清理caches里下了一半的残骸。
排查顺序我一般这么走:
- 用
curl -I直接打那个 URL,看是 DNS 解析失败、连接被拒还是响应超时; - 如果是超时,先确认是不是所有请求都超时(换一个公开地址试试),以此判断是全局网络问题还是单个地址问题;
- 检查
gradle.properties里的超时设置是不是太小,默认的 30 秒在网络抖动大的环境里确实不够; - 如果是依赖解析超时,
--refresh-dependencies重跑一次,强制刷新元数据,很多时候残存的过期索引会导致反复重试。
提示:
caches/modules-2里存在.part之类的中间文件时,删除对应模块目录再重新构建,比反复重试有效得多。
5.2 Could not install Gradle distribution:五种典型成因逐个排
这个报错的完整形态通常是Could not install Gradle distribution from 'xxx',后面跟一个原因。按我遇到的频率排个序:
| 成因 | 典型表现 | 处理方式 |
|---|---|---|
| 地址 404 | 后面跟FileNotFoundException | 版本号拼错、镜像站没收录该版本、用了 rc 版 |
| 压缩包损坏 | 解压阶段报错,哈希不匹配 | 重新下载,用.sha256校验 |
| 磁盘空间不足 | 解压到一半失败 | 清理dists和caches,或迁移GRADLE_USER_HOME |
| 文件被占用 | Windows 上偶发,重试就好 | 关掉其他 Gradle 进程,删掉半成品目录 |
| 权限问题 | 目录只读或被杀软锁住 | 检查dists目录权限,加白名单 |
排查这个错的最有效手段,是绕过 Gradle 直接用命令行下这个包。curl -O加上.sha256校验,两步就能确认到底是地址问题还是环境问题。如果命令行能下、Gradle 下不了,那大概率是本机代理设置、证书或者杀毒软件在作祟;如果命令行也下不了,那就是地址本身不行,换镜像。
5.3 Could not resolve gradle:gradle:8.7 到底在解析什么东西
caused by: org.gradle.internal.resolve.moduleversionresolveexception: could not resolve gradle:gradle:8.7这个报错,第一次看到的人都会困惑:"我明明已经装好 Gradle 了,为什么还要解析 gradle:gradle?"
这通常出现在buildscript的 classpath 声明里,或者某个老项目的build.gradle中显式写了classpath 'gradle:gradle:8.7'之类的坐标。它解析的不是 Gradle 发行版本身,而是把 Gradle 当成一个普通的 Maven 构件去仓库里找。这个坐标在 Maven Central 上并不存在对应版本,或者你的repositories里压根没有配能提供它的仓库。
处理方法分两步:
- 全局搜一下
build.gradle和gradle.properties里有没有奇怪的gradle:gradle坐标,有的话直接删掉,绝大多数项目不需要它; - 如果确实需要 Gradle API 做插件开发,正确做法是用
gradleApi()这个依赖,而不是写死 Maven 坐标:
dependencies { implementation gradleApi() }顺带说一句,settings.gradle里的pluginManagement块如果没有配repositories,插件解析会失败,表现形式也可能是这类 resolve 异常。补齐google()、mavenCentral()、gradlePluginPortal()三个仓库通常能解决。
5.4 Flutter 的 apply plugin 警告与 JDK/Gradle 版本错配
Flutter 项目近两年最常见的一条提示是:
You are applying Flutter's main Gradle plugin imperatively using the apply script method这不是错误,是警告,意思是你用的是旧的命令式写法,未来某个版本会移除。迁移的落点在android/settings.gradle,改成声明式加载:
// android/settings.gradle pluginManagement { def flutterSdkPath = { def properties = new Properties() file("local.properties").withInputStream { properties.load(it) } def flutterSdkPath = properties.getProperty("flutter.sdk") assert flutterSdkPath != null, "flutter.sdk not set in local.properties" return flutterSdkPath }() includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") repositories { google() mavenCentral() gradlePluginPortal() } } plugins { id "dev.flutter.flutter-plugin-loader" version "1.0.0" id "com.android.application" version "8.6.1" apply false id "org.jetbrains.kotlin.android" version "2.0.20" apply false }对应的android/app/build.gradle顶部改成:
plugins { id "com.android.application" id "kotlin-android" id "dev.flutter.flutter-gradle-plugin" }另一类提示是关于 JDK 与 Gradle 版本不匹配的,比如构建输出里出现Your build is currently configured to use Java 21.0.4 and Gradle 8.x这类信息。这不是致命错误,但会提示你两者兼容性存在风险。经验对应关系大致如下(以官方兼容性矩阵为准):
| Gradle 版本 | 可用的最高 JDK |
|---|---|
| 8.8 | JDK 22 |
| 8.5 ~ 8.7 | JDK 21 |
| 8.3 ~ 8.4 | JDK 20 |
| 7.6 ~ 8.2 | JDK 19 及以下 |
| 7.3 ~ 7.5 | JDK 17 |
真要在一个项目里固定 JDK,可以在gradle.properties里指定:
org.gradle.java.home=/Library/Java/JavaVirtualMachines/jdk-17.jdk/Contents/Home这行的好处是不受系统JAVA_HOME影响,坏处是路径写死了,换机器就报错。所以它适合放在本机全局的~/.gradle/gradle.properties,而不是提交到项目里。
6. 离线与版本治理:离线包分发、VersionCatalog 与 Gradle/Maven 的取舍
6.1 完全离线的机器怎么把构建跑起来
完全不联网的机器要跑 Gradle 构建,需要同时满足两件事:发行版在本地、依赖在本地缓存里。这两件事要分开准备。
发行版这一块,用第 3.3 节说的dists目录方案,把 zip 放进哈希目录,让 Gradle 自己解压。如果有多个项目用不同 Gradle 版本,就多准备几个。
依赖这一块,思路是在一台联网机器上先把依赖全部拉下来,然后把整个~/.gradle/caches/modules-2目录拷贝过去。要注意的是,modules-2里同时有元数据索引和实际文件,直接整目录拷贝是可行的,但版本多的项目这个目录可能有几个 GB,准备一个移动硬盘比较现实。
拷完之后,构建时加--offline参数:
./gradlew assembleDebug --offline--offline的含义是"只用本地缓存,不许联网"。如果仍然报错说找不到某个依赖,说明那台联网机器上没有把对应配置解析过,需要先在联网机器上跑一次完整的./gradlew build,确保所有配置下的依赖都被解析到,再拷缓存。
提示:离线场景下,
caches/modules-2/files-2.1/里的文件是按模块和哈希分目录存的,拷贝时建议用压缩包或 rsync 类的工具整体拷贝,逐个文件复制容易出现元数据与文件不匹配的情况。
6.2 VersionCatalog 统一版本的一条落地路径
Gradle 7.4 之后引入的 VersionCatalog,落地形式是在gradle/libs.versions.toml里集中声明版本。对多模块项目来说,这是减少版本号散落各处的有效手段。
[versions] agp = "8.6.1" kotlin = "2.0.20" springBoot = "3.3.4" junit = "5.10.2" [libraries] kotlin-stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib", version.ref = "kotlin" } junit-jupiter = { module = "org.junit.jupiter:junit-jupiter", version.ref = "junit" } [plugins] android-application = { id = "com.android.application", version.ref = "agp" } kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } spring-boot = { id = "org.springframework.boot", version.ref = "springBoot" }在build.gradle.kts里引用:
plugins { alias(libs.plugins.android.application) alias(libs.plugins.kotlin.android) } dependencies { implementation(libs.kotlin.stdlib) testImplementation(libs.junit.jupiter) }在 Groovy DSL 里写法略有不同:
plugins { alias(libs.plugins.android.application) }实际落地时有个小坑:libs这个访问器只有在libs.versions.toml位于gradle/目录下时才会自动生成。放到别的位置需要在settings.gradle里手动声明:
dependencyResolutionManagement { versionCatalogs { libs { from(files("config/versions.toml")) } } }改用 Catalog 之后,团队里"某个模块悄悄用了另一个版本"这类问题基本就消失了,代价是所有版本改动都要动同一个文件,合并冲突概率上升。我的做法是给这个文件单独设一个 owner,或者约定改版本必须单独提一次变更,避免和业务代码混在一起。
6.3 Gradle 项目与 Maven 构建的 Spring Boot 项目差异
不少人是从 Maven 转过来的,看到build.gradle会有点不知所措。两者的核心差异不只在 DSL 语法,更在构建模型:
| 维度 | Maven | Gradle |
|---|---|---|
| 构建模型 | 固定生命周期阶段(compile、test、package) | 任务图(Task Graph),任务间依赖决定执行顺序 |
| 配置语言 | XML | Groovy DSL 或 Kotlin DSL |
| 依赖配置 | scope(compile、runtime、test) | 配置(implementation、api、testImplementation) |
| 增量构建 | 有限,靠插件 | 内建,任务级输入输出对比 |
| 构建缓存 | 不内建 | 内建,可跨机器共享 |
| 生态成熟度 | 稳定成熟,企业级插件多 | 灵活强大,Android 生态标配 |
对于 Spring Boot 项目,用 Gradle 构建时依赖版本通常由 Spring Boot 的 BOM 统一管理,写法是:
plugins { id 'org.springframework.boot' version '3.3.4' id 'io.spring.dependency-management' version '1.1.6' } dependencies { implementation 'org.springframework.boot:spring-boot-starter-web' // 不写版本号,由 BOM 决定 }而 Maven 那边靠的是spring-boot-starter-parent或者dependencyManagement导入 BOM。两者在版本管理思路上是一致的,差别只在于表达方式。
我的实际取舍是:Android 和 Kotlin 项目用 Gradle,传统 Java 后端服务用 Maven。理由不是性能,而是团队熟悉度。构建工具的切换成本在写脚本之外,更多在团队所有人的肌肉记忆上,为了省下那点构建时间让全员重新学一套 DSL,性价比不高。真要用 Gradle 写 Spring Boot,bootRun、bootJar这些任务名称跟 Maven 的spring-boot:run、repackage对应起来看,迁移成本其实没想象中那么大。
最后分享一个我用了很久的小习惯:在本机建一个build-tools目录,把常用的几个 Gradle 版本 zip、常用 JDK 安装包、常用 Node 版本包都放进去,配上校验过的哈希清单。换机器、装新环境、给同事搭环境的时候,直接从里面拿,不用再去各处找地址。这个目录一年能帮我省下的等待时间,比任何构建优化都实在。