ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Gradle构建加速实战:国内镜像与性能调优指南

Gradle构建加速实战:国内镜像与性能调优指南 1. 为什么你的 Gradle 构建慢如蜗牛如果你在国内做 Android 或者 Java 后端开发大概率经历过这样的场景新建一个项目点下 Sync Now然后去泡杯咖啡回来发现进度条还卡在“Downloading gradle-x.x.x-all.zip”上。再等一会儿直接给你弹一个java.net.SocketTimeoutException心态当场就炸了。这不是你的网络有问题也不是电脑配置不够。核心原因就一个Gradle 默认的 distributionUrl 指向的是国外的服务节点国内访问速度极慢甚至直接超时。再加上项目依赖的 Maven 仓库默认走的是 Google 和 Maven Central这两个源在国内的访问质量同样堪忧。所以这篇内容就是把我这些年在国内环境下折腾 Gradle 加速的经验完整梳理一遍。从 Wrapper 的 distributionUrl 替换到依赖仓库的国内镜像配置再到离线包的妙用和一些常见的坑全都聊透。不管你是刚接触 Gradle 的新手还是被构建速度折磨已久的老手都能从这里找到可以直接抄作业的方案。2. Gradle Wrapper 下载加速distributionUrl 的正确替换姿势2.1 先搞清楚 Gradle Wrapper 到底在干什么很多人用了很久 Gradle但对 Wrapper 的机制其实是一知半解的。你项目根目录下有个gradle/wrapper/gradle-wrapper.properties文件里面有一行distributionUrl这个就是 Wrapper 的核心。当你执行./gradlew build或者点 Android Studio 的 Sync 时Wrapper 会先检查本地有没有对应版本的 Gradle。如果没有它就会根据distributionUrl去下载。下载完成后解压到本地缓存目录Linux/Mac 下是~/.gradle/wrapper/dists/Windows 下是%USERPROFILE%\.gradle\wrapper\dists\然后才用这个 Gradle 来执行构建。问题就出在默认的distributionUrl上。它长这样distributionUrlhttps\://services.gradle.org/distributions/gradle-8.4-bin.zipservices.gradle.org这个域名在国内的访问速度说实话看运气。有时候能跑到几百 KB/s有时候直接连接超时。而且它不支持断点续传一旦中断就得从头再来一个 100 多 MB 的包下到你怀疑人生。2.2 替换为国内镜像地址的实操方案最直接的解决办法就是把distributionUrl换成国内镜像。目前比较稳定的方案是使用腾讯云或阿里云的镜像。腾讯云的 Gradle 镜像地址格式是这样的distributionUrlhttps\://mirrors.cloud.tencent.com/gradle/gradle-8.4-bin.zip阿里云的地址格式distributionUrlhttps\://mirrors.aliyun.com/macports/distfiles/gradle/gradle-8.4-bin.zip注意这里的版本号必须和你项目实际需要的 Gradle 版本严格对应。你可以在gradle-wrapper.properties里看到原本的版本号直接把域名部分替换掉就行。注意替换的时候一定要保留https\://中的反斜杠转义符这是 properties 文件的语法要求少了这个反斜杠会导致解析失败。我实测下来腾讯云的镜像在国内大部分地区都能跑到 5-10 MB/s一个 100 MB 的包十几秒就下完了。阿里云的镜像也不错但偶尔会有同步延迟某些新版本可能还没同步上去。所以我的建议是优先用腾讯云如果发现某个版本没有再换阿里云试试。2.3 手动下载离线包的终极方案如果你连镜像都下不动或者公司网络有严格的限制那就只能用最原始但最可靠的办法手动下载离线包。操作步骤很简单找一台能正常访问的机器或者用浏览器直接打开镜像地址把gradle-x.x.x-bin.zip下载到本地找到本地的 Gradle 缓存目录一般在~/.gradle/wrapper/dists/下面你会看到一个以 Gradle 版本号命名的文件夹比如gradle-8.4-bin进去后有一串随机字符命名的子文件夹把下载好的 zip 包直接丢进这个随机字符命名的文件夹里重新执行构建Wrapper 会检测到本地已经有对应的包直接解压使用不再走网络下载这里有个细节很多人不知道你不需要手动解压那个 zip 包。Wrapper 自己会检测并解压。如果你手动解压了反而可能导致目录结构不对Wrapper 识别不了。实操心得那个随机字符命名的文件夹名字每次可能不一样如果你不确定该放哪个文件夹可以先执行一次构建让它创建目录哪怕下载失败也没关系然后去dists目录下找最新创建的那个文件夹就对了。3. 依赖仓库加速让 Maven 依赖飞起来3.1 默认仓库为什么慢Gradle 下载完自身之后接下来要做的就是解析项目依赖。默认情况下你的build.gradle或者settings.gradle里会配置google()和mavenCentral()这两个仓库。这两个仓库的服务器都在国外国内访问的延迟很高而且经常出现某个依赖下载到一半卡住的情况。一个中型 Android 项目依赖数量轻松上百个。如果每个依赖都要走国外源构建时间会被拉得非常长。更糟糕的是Gradle 默认不会对依赖下载做很好的超时处理有时候一个依赖卡住了整个构建就挂在那里你也不知道到底是在下载还是已经死了。3.2 国内镜像仓库的配置方法解决办法就是把仓库地址替换成国内镜像。目前主流的方案有阿里云、腾讯云、华为云等。我以阿里云为例因为它的 Maven 镜像覆盖最全更新也最及时。在settings.gradle中配置这是新版 Gradle 推荐的方式dependencyResolutionManagement { repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) 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 } maven { url https://maven.aliyun.com/repository/spring } maven { url https://maven.aliyun.com/repository/spring-plugin } maven { url https://maven.aliyun.com/repository/grails-core } maven { url https://maven.aliyun.com/repository/apache-snapshots } } }如果你用的是老版本 Gradle在build.gradle的allprojects块里配置allprojects { 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 } google() mavenCentral() } }这里有个策略上的考量国内镜像放在前面原始仓库放在后面。这样大部分依赖会从国内镜像下载少数镜像上没有的依赖会回退到原始仓库。虽然回退的那部分还是会慢但至少不会因为一个依赖卡住整个构建。3.3 仓库配置的优先级与取舍阿里云的 Maven 镜像有好几个仓库地址每个对应不同的上游源。public是聚合仓库包含了 Maven Central 的大部分内容google对应 Google 的 Android 仓库gradle-plugin对应 Gradle 插件仓库。你不需要把所有仓库都加上加太多反而会拖慢依赖解析速度因为 Gradle 会按顺序逐个仓库去查找。我的建议是Android 项目publicgooglegradle-plugin三个就够了纯 Java 后端项目publicgradle-plugin两个就够Spring 项目额外加上spring和spring-plugin注意事项有些公司内部有私有仓库比如 Nexus这种情况下国内镜像和私有仓库的配置顺序需要仔细考虑。一般来说私有仓库应该放在最前面因为内部依赖只有私有仓库才有。如果放在后面Gradle 会先去国内镜像找一圈找不到才去私有仓库白白浪费时间。4. Gradle 构建性能调优不只是下载快4.1 开启 Gradle 守护进程和并行构建下载加速只是第一步。依赖下载完之后实际的编译过程也有很大的优化空间。在gradle.properties文件里项目根目录下没有就新建一个加上这几行org.gradle.daemontrue org.gradle.paralleltrue org.gradle.cachingtrue org.gradle.configureondemandtrueorg.gradle.daemontrue让 Gradle 守护进程常驻内存。这样第二次构建的时候不需要重新启动 JVM、重新加载插件启动时间能省好几秒。org.gradle.paralleltrue让多模块项目可以并行构建充分利用多核 CPU。org.gradle.cachingtrue开启构建缓存相同的输入不会重复构建。这几个配置加起来在多模块项目上的效果非常明显。我一个 8 模块的项目开启前后构建时间从 2 分多钟降到了 40 多秒。4.2 JVM 内存参数怎么调Gradle 本身跑在 JVM 上默认的堆内存可能不够用尤其是大型项目。你可以在gradle.properties里调整org.gradle.jvmargs-Xmx4096m -XX:MaxMetaspaceSize1024m -XX:HeapDumpOnOutOfMemoryError -Dfile.encodingUTF-8-Xmx4096m表示最大堆内存 4GB。这个值要根据你机器的实际内存来定。一般来说8GB 内存的机器给 2-3GB16GB 的给 4-6GB 比较合适。给太大反而会导致 GC 时间变长得不偿失。-XX:MaxMetaspaceSize1024m是元空间大小Gradle 加载大量插件的时候元空间消耗比较大给 1GB 是比较稳妥的。实操心得如果你不确定该给多少内存可以先不加这个配置跑一次构建然后用jvisualvm或者jconsole连上去看看实际用了多少堆内存再根据峰值往上加 20%-30% 就行。4.3 构建缓存与增量编译的配合Gradle 的构建缓存Build Cache和增量编译是两个不同的机制但配合起来效果很好。增量编译是 Gradle 默认开启的它只重新编译改动的源文件。构建缓存则是把构建产物缓存起来如果输入没变直接复用缓存结果连编译都省了。构建缓存可以配置本地缓存和远程缓存。本地缓存默认在~/.gradle/caches/build-cache-1/下。远程缓存需要搭建一个缓存服务器团队协作的时候很有用但个人开发用本地缓存就够了。org.gradle.cachingtrue这一行开启本地构建缓存。如果你在 CI 环境或者团队协作场景下可以考虑配置远程缓存org.gradle.cachingtrue org.gradle.cache.remote.urlhttp://your-cache-server/cache/不过远程缓存的搭建和维护成本不低小团队建议先用本地缓存。5. 常见报错与排查技巧实录5.1 distributionUrl 相关的典型报错报错一Could not install Gradle distribution from reason: java.net.SocketTimeoutException这个是最常见的。原因就是下载 Gradle 分发包超时了。解决办法就是前面说的替换distributionUrl为国内镜像或者手动下载离线包放到缓存目录。报错二Could not install Gradle distribution from reason: java.net.UnknownHostException这个通常是 DNS 解析问题。services.gradle.org在某些网络环境下可能解析不了。换成国内镜像地址就能解决。报错三下载到一半卡住不动Gradle 的下载没有断点续传机制一旦卡住只能取消重来。如果反复出现建议直接手动下载离线包。5.2 依赖解析失败的排查思路依赖解析失败的表现形式很多常见的有Could not resolve、Could not find、Could not download等。排查思路可以按这个顺序来确认仓库地址配置是否正确国内镜像的 URL 有没有写错确认依赖的版本号是否存在有些版本可能只在 Google 仓库有国内镜像还没同步尝试在浏览器里直接访问依赖的 URL看看能不能下载如果国内镜像没有临时把原始仓库加回去试试避坑技巧阿里云镜像虽然覆盖很全但偶尔会有同步延迟。如果你发现某个依赖在国内镜像上找不到不要急着怀疑配置有问题先去 Maven Central 的官网确认一下这个依赖是否真的存在然后再检查镜像同步状态。5.3 Gradle 版本与插件兼容性问题报错error: gradle dsl method not found: minSdkVersion()这个报错通常出现在 Android 项目中原因是 Gradle 版本和 Android Gradle PluginAGP版本不匹配。minSdkVersion()这个方法在新版 AGP 中已经被废弃了改成了minSdk。你需要检查build.gradle里的语法是否和当前 AGP 版本匹配。报错Deprecated Gradle features were used in this build, making it incompatible with Gradle 8.0这是警告不是错误但说明你用的某些 Gradle 特性在新版本中会被移除。解决办法是找到对应的废弃 API替换成新的写法。常见的比如compile换成implementationtestCompile换成testImplementation等。报错You are applying Flutters main Gradle plugin imperatively using the apply script method这是 Flutter 项目的特有报错。Flutter 的 Gradle 插件配置方式在新版本中发生了变化需要从apply plugin的方式改成pluginsDSL 的方式。具体来说在settings.gradle里加上plugins { id dev.flutter.flutter-plugin-loader version 1.0.0 id com.android.application version 8.1.0 apply false id org.jetbrains.kotlin.android version 1.9.0 apply false }然后在app/build.gradle里用plugins块来应用插件。5.4 常见问题速查表报错信息根本原因解决方案SocketTimeoutException下载 Gradle 分发包超时替换 distributionUrl 为国内镜像UnknownHostExceptionDNS 解析失败检查网络或换镜像地址Could not resolve依赖仓库配置错误检查仓库 URL 和依赖版本DSL method not foundGradle 与插件版本不匹配对齐 Gradle 和 AGP 版本Deprecated features使用了废弃 API替换为新的 API 写法Flutter plugin apply 报错Flutter 插件配置方式过时改用 plugins DSL 方式6. 一套完整的加速配置模板6.1 gradle-wrapper.properties 模板distributionBaseGRADLE_USER_HOME distributionPathwrapper/dists distributionUrlhttps\://mirrors.cloud.tencent.com/gradle/gradle-8.4-bin.zip networkTimeout10000 validateDistributionUrltrue zipStoreBaseGRADLE_USER_HOME zipStorePathwrapper/dists这里加了networkTimeout10000单位是毫秒表示 10 秒超时。默认值比较长设置短一点可以更快地发现网络问题并触发重试。6.2 gradle.properties 模板# JVM 参数 org.gradle.jvmargs-Xmx4096m -XX:MaxMetaspaceSize1024m -Dfile.encodingUTF-8 # 构建性能 org.gradle.daemontrue org.gradle.paralleltrue org.gradle.cachingtrue org.gradle.configureondemandtrue # Android 相关 android.useAndroidXtrue android.enableJetifiertrue6.3 settings.gradle 仓库配置模板pluginManagement { repositories { maven { url https://maven.aliyun.com/repository/gradle-plugin } maven { url https://maven.aliyun.com/repository/google } maven { url https://maven.aliyun.com/repository/public } gradlePluginPortal() google() mavenCentral() } } dependencyResolutionManagement { repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) 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 } google() mavenCentral() } }注意pluginManagement块里的仓库配置这个很多人会忽略。Gradle 插件本身的下载也走仓库如果这里不配国内镜像插件下载同样会很慢。6.4 配置生效的验证方法配置改完之后怎么确认真的生效了几个验证方法执行./gradlew --version看看输出的 Gradle 版本是否正确执行./gradlew build --info在日志里搜索下载相关的信息看看实际用的是哪个 URL观察构建时间对比配置前后的差异在~/.gradle/wrapper/dists/目录下确认分发包是否已经下载成功实操心得改完配置后第一次构建建议加上--refresh-dependencies参数强制刷新依赖缓存确保新的仓库配置生效。之后正常构建就不需要这个参数了。7. 一些容易被忽略的细节7.1 代理配置的正确写法有些公司网络环境需要走代理才能访问外网。Gradle 支持通过gradle.properties配置代理systemProp.http.proxyHostyour-proxy-host systemProp.http.proxyPort8080 systemProp.https.proxyHostyour-proxy-host systemProp.https.proxyPort8080 systemProp.http.nonProxyHostslocalhost|127.0.0.1|*.your-company.comnonProxyHosts这个配置很重要它指定了哪些地址不走代理。本地地址和公司内部地址一定要加进去否则访问内部仓库的时候会绕一圈代理反而更慢。7.2 多项目构建的仓库继承问题在多模块项目中子模块的仓库配置默认会继承根项目的配置。但如果你在子模块里单独配置了repositories它会覆盖根项目的配置。这就会导致一个问题根项目配了国内镜像但某个子模块自己配了mavenCentral()结果这个子模块的依赖还是走国外源。解决办法是在根项目的settings.gradle里用dependencyResolutionManagement的FAIL_ON_PROJECT_REPOS模式强制所有子模块使用统一的仓库配置。如果某个子模块确实需要额外的仓库可以在dependencyResolutionManagement里统一添加而不是在子模块里单独配置。7.3 Gradle 缓存目录的清理与迁移Gradle 的缓存目录默认在~/.gradle/下时间长了会变得非常大几个 GB 甚至十几个 GB都很正常。如果你 C 盘空间紧张可以把缓存目录迁移到其他盘。设置环境变量GRADLE_USER_HOME指向新的目录就行。在 Windows 上可以通过系统属性设置在 Linux/Mac 上可以在.bashrc或.zshrc里加export GRADLE_USER_HOME/path/to/your/gradle/home迁移之后记得把原来的缓存目录内容复制过去否则所有依赖都要重新下载。清理缓存的话不要直接删整个.gradle目录那样会把 Wrapper 的分发包也删掉。正确的做法是删~/.gradle/caches/目录下的内容保留wrapper目录。7.4 离线模式的合理使用Gradle 有一个离线模式--offline开启后不会去网络下载任何东西只用本地缓存。这个模式在以下场景很有用网络环境不稳定频繁超时依赖已经全部下载好了不需要更新CI 环境中依赖已经预缓存但离线模式也有坑如果本地缓存不完整构建会直接失败而且报错信息可能不太直观。所以用离线模式之前先确保所有依赖都已经下载好了。./gradlew build --offline我个人的习惯是日常开发不开离线模式让 Gradle 自动检查更新但在网络特别差的时候或者做演示的时候会临时开启离线模式保证构建稳定。8. 从构建日志里挖出性能瓶颈8.1 用 --profile 生成构建报告Gradle 提供了一个--profile参数可以生成一份详细的构建报告告诉你每个阶段花了多少时间。./gradlew build --profile执行完成后会在build/reports/profile/目录下生成一个 HTML 报告。打开后可以看到总构建时间各阶段的耗时分布配置阶段、依赖解析、任务执行等每个任务的执行时间哪些任务被跳过了UP-TO-DATE这个报告是定位性能瓶颈的利器。如果你发现依赖解析阶段花了特别长时间说明仓库配置还有优化空间如果任务执行阶段慢可能是某个编译任务或者测试任务拖了后腿。8.2 用 --scan 做深度分析Gradle 还有一个--scan参数可以把构建数据上传到 Gradle 官方的分析平台生成更详细的报告。不过这个功能需要联网而且数据会上传到国外服务器国内访问可能不太稳定。./gradlew build --scan如果你能正常访问这个报告比--profile详细得多包括依赖下载耗时、缓存命中率、JVM 性能指标等。但考虑到国内网络环境我一般还是用--profile就够了。8.3 构建时间的基线对比方法优化效果好不好不能凭感觉要有数据对比。我的做法是优化前连续执行 3 次./gradlew clean build记录每次的总时间取平均值作为基线应用优化配置再连续执行 3 次./gradlew clean build记录时间取平均值对比两个平均值算出提升比例为什么要执行 3 次取平均因为单次构建时间受很多因素影响比如系统负载、磁盘 IO、网络波动等。取多次平均可以减少偶然误差。实操心得第一次构建通常是最慢的因为要下载依赖、建立缓存。从第二次开始才是真实的构建速度。所以做基线对比的时候一定要先跑一次“预热”然后再开始计时。9. 不同场景下的配置策略9.1 个人开发机的配置重点个人开发机通常网络环境相对自由配置的重点是替换 distributionUrl 为国内镜像解决 Gradle 自身下载问题配置国内 Maven 镜像加速依赖下载开启守护进程和并行构建提升日常构建速度适当调整 JVM 内存避免 OOM个人开发机不需要考虑太复杂的代理和私有仓库配置保持简单直接就好。9.2 公司内网环境的特殊处理公司内网环境通常有更严格的网络限制可能需要配置 HTTP 代理才能访问外网使用公司内部的 Nexus 或 Artifactory 作为依赖仓库某些依赖只能从内部仓库获取这种情况下仓库配置的顺序很关键内部仓库放最前面国内镜像放中间原始仓库放最后。同时nonProxyHosts要配置好确保访问内部仓库不走代理。9.3 CI/CD 环境的优化思路CI/CD 环境和开发机不同每次构建都是全新的环境没有本地缓存。所以优化的重点在于预缓存 Gradle 分发包和依赖避免每次构建都重新下载使用远程构建缓存复用之前的构建产物配置合理的超时和重试策略避免网络波动导致构建失败很多 CI 平台支持缓存目录配置把~/.gradle/caches/和~/.gradle/wrapper/缓存起来能大幅减少构建时间。10. 我踩过的那些坑说几个我实际踩过的坑都是文档里不会写的。第一个坑镜像地址写错了版本号。有一次我把distributionUrl改成了腾讯云镜像但版本号写的是gradle-8.4-all.zip而项目实际需要的是gradle-8.4-bin.zip。结果 Wrapper 去下载的时候发现文件不存在报了一个很模糊的错误排查了半天才发现是文件名不对。bin和all的区别在于all包含源码和文档体积大很多一般用bin就够了。第二个坑仓库配置顺序导致依赖解析变慢。我一开始把google()和mavenCentral()放在了国内镜像前面想着“先查官方源找不到再查镜像”。结果每次构建都要先去国外源查一圈超时之后才去国内镜像反而更慢了。后来把国内镜像提到前面构建时间直接降了一半。第三个坑gradle.properties 里的配置被覆盖了。我在项目根目录的gradle.properties里配了 JVM 内存参数但构建的时候发现没生效。后来发现是~/.gradle/gradle.properties里也有一份配置而且优先级更高。Gradle 的配置优先级是命令行参数 项目级 gradle.properties 用户级 gradle.properties。所以如果你发现配置不生效检查一下用户级配置文件里是不是有冲突的配置。第四个坑手动解压了离线包。前面提过手动下载的离线包不要解压直接放到缓存目录就行。我有一次手贱解压了结果 Wrapper 识别不了又重新下载了一遍。后来才知道 Wrapper 是通过检查 zip 文件的哈希值来判断是否已经下载完成的解压后 zip 文件没了它自然就认为没下载过。第五个坑忘了配置 pluginManagement 的仓库。项目依赖的仓库配了国内镜像但插件的仓库没配。结果依赖下载很快但插件下载还是慢。后来在settings.gradle的pluginManagement块里也加了国内镜像才彻底解决。这些坑说到底都是一些细节问题但往往就是这些细节决定了构建体验的好坏。希望你看完之后能少走一些弯路。
返回列表