本章目标
专家级 Gradle 能力不是会写更多语法,而是能让大型工程构建"快、稳、可治理、可追踪、可演进"。
本章覆盖:
- 构建性能诊断与 Build Scan 深度使用
- 依赖治理与依赖锁定
- 依赖校验(Dependency Verification)
- 代码质量接入(Checkstyle / SpotBugs)
- 测试覆盖率(JaCoCo)
- 自定义 Gradle 插件开发(独立插件)
- Gradle TestKit(测试插件)
- 制品发布与私服
- Android/大型多模块工程治理
- 疑难问题排查
1. 构建性能分析
Profile 报告
bash
gradle clean build --profile
生成在 build/reports/profile/,是本地 HTML 报告。
Build Scan(推荐)
bash
gradle clean build --scan
第一次会提示接受协议,之后生成在线报告链接(gradle.com)。
Build Scan 能看到:
- 每个 task 耗时明细。
- 哪些 task 是 FROM-CACHE / UP-TO-DATE / EXECUTED。
- 依赖解析明细。
- 测试结果。
- 构建时的环境信息。
关注指标:
| 指标 | 说明 | 优化方向 |
|---|---|---|
| Configuration time | 配置阶段耗时 | 懒配置、减少根脚本逻辑、配置缓存 |
| Task execution time | task 执行耗时 | 增量构建、缓存、自定义 task 输入输出 |
| Dependency resolution | 依赖解析耗时 | 固定版本、减少动态版本、仓库治理 |
| Avoidable work | 可避免工作比例 | 缓存命中率优化 |
Gradle Enterprise / Develocity
企业级版本,支持:
- 构建历史对比。
- 团队级缓存(Build Cache Node)。
- 构建性能趋势看板。
- 失败根因分析。
接入(需要服务端):
groovy
// settings.gradle
plugins {
id 'com.gradle.enterprise' version '3.17'
}
gradleEnterprise {
buildScan {
server = 'https://ge.your-company.com'
publishAlways()
}
}
2. 大型工程优化顺序
不要一上来就改复杂插件,建议按顺序治理:
- 固定 JDK、Gradle、插件版本(Wrapper + Toolchain)。
- 开启并行构建和构建缓存。
- 清理动态版本依赖(禁止
1.+、latest.release)。 - 抽取 Convention Plugin,减少子模块重复配置。
- 检查配置阶段耗时(
--profile)。 - 验证配置缓存兼容性(
--configuration-cache)。 - 对慢 task 做输入输出声明和增量改造。
- 接入 Build Scan 持续监控。
gradle.properties 推荐配置:
properties
org.gradle.jvmargs=-Xmx4g -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError
org.gradle.parallel=true
org.gradle.caching=true
org.gradle.configuration-cache=true
org.gradle.warning.mode=all
3. 依赖治理
常用命令
bash
gradle :app:dependencies --configuration runtimeClasspath
gradle :app:dependencyInsight --dependency log4j --configuration runtimeClasspath
gradle dependencies --write-locks # 生成锁文件
gradle dependencies --update-locks # 更新锁文件中的特定依赖
依赖锁定
groovy
dependencyLocking {
lockAllConfigurations()
}
生成锁文件:
bash
gradle dependencies --write-locks
锁文件(gradle/dependency-locks/runtimeClasspath.lockfile):
text
# This is a Gradle generated file for dependency locking.
com.google.guava:guava:33.2.1-jre=runtimeClasspath
org.junit.jupiter:junit-jupiter:5.10.2=testRuntimeClasspath
适用场景:
- 金融、支付、政企等高稳定性项目。
- 对供应链安全要求高的项目。
- 希望 CI 和本地依赖解析完全一致的项目。
治理策略
| 问题 | 处理方式 |
|---|---|
| 版本散落 | Version Catalog |
| 传递依赖冲突 | dependencyInsight 定位来源 |
| 老版本安全风险 | constraints 或 platform 统一 |
| 动态版本不可重复 | 禁止 1.+、latest.release |
| 仓库来源混乱 | 在 settings.gradle 统一声明 |
4. 依赖校验(Dependency Verification)
Dependency Verification 通过校验文件的 SHA 哈希防止供应链攻击。
生成校验文件:
bash
gradle --write-verification-metadata sha256 help
生成 gradle/verification-metadata.xml:
xml
<?xml version="1.0" encoding="UTF-8"?>
<verification-metadata>
<configuration>
<verify-metadata>true</verify-metadata>
<verify-signatures>false</verify-signatures>
</configuration>
<components>
<component group="com.google.guava" name="guava" version="33.2.1-jre">
<artifact name="guava-33.2.1-jre.jar">
<sha256 value="a4d5...hash..." origin="Generated by Gradle"/>
</artifact>
</component>
</components>
</verification-metadata>
之后每次构建都会校验 jar 的哈希,不匹配则构建失败。
5. 代码质量接入
Checkstyle
groovy
plugins {
id 'checkstyle'
}
checkstyle {
toolVersion = '10.17.0'
configFile = file("${rootDir}/config/checkstyle/checkstyle.xml")
maxWarnings = 0
}
tasks.withType(Checkstyle).configureEach {
reports {
xml.required = true
html.required = true
}
}
config/checkstyle/checkstyle.xml 示例:
xml
<?xml version="1.0"?>
<!DOCTYPE module PUBLIC
"-//Checkstyle//DTD Checkstyle Configuration 1.3//EN"
"https://checkstyle.org/dtds/configuration_1_3.dtd">
<module name="Checker">
<module name="TreeWalker">
<module name="WhitespaceAround"/>
<module name="NeedBraces"/>
<module name="EqualsHashCode"/>
<module name="MagicNumber"/>
</module>
</module>
执行:
bash
gradle checkstyleMain # 检查主代码
gradle checkstyleTest # 检查测试代码
gradle check # 包含所有检查
SpotBugs
groovy
plugins {
id 'com.github.spotbugs' version '6.0.18'
}
spotbugs {
toolVersion = '4.8.6'
effort = 'max'
reportLevel = 'medium'
excludeFilter = file("${rootDir}/config/spotbugs/exclude.xml")
}
tasks.withType(com.github.spotbugs.snom.SpotBugsTask).configureEach {
reports {
html.required = true
xml.required = false
}
}
在 Convention Plugin 中统一接入
com.example.quality-conventions.gradle:
groovy
plugins {
id 'checkstyle'
id 'jacoco'
}
checkstyle {
toolVersion = '10.17.0'
configFile = rootProject.file('config/checkstyle/checkstyle.xml')
maxWarnings = 0
}
jacoco {
toolVersion = '0.8.12'
}
tasks.named('test') {
finalizedBy tasks.named('jacocoTestReport')
}
tasks.named('jacocoTestReport') {
dependsOn tasks.named('test')
reports {
xml.required = true
html.required = true
}
}
6. 测试覆盖率(JaCoCo)
groovy
plugins {
id 'java'
id 'jacoco'
}
jacoco {
toolVersion = '0.8.12'
}
test {
useJUnitPlatform()
finalizedBy jacocoTestReport
}
jacocoTestReport {
dependsOn test
reports {
xml.required = true
html.required = true
csv.required = false
}
}
// 设置覆盖率门禁
jacocoTestCoverageVerification {
violationRules {
rule {
limit {
minimum = 0.80 // 行覆盖率不低于 80%
}
}
}
}
check.dependsOn jacocoTestCoverageVerification
执行:
bash
gradle test jacocoTestReport # 执行测试并生成报告
gradle jacocoTestCoverageVerification # 验证覆盖率是否达标
报告位置:build/reports/jacoco/test/html/index.html
多模块汇总覆盖率:
groovy
// 根模块 build.gradle
tasks.register('jacocoRootReport', JacocoReport) {
dependsOn subprojects.collect { it.tasks.named('test') }
sourceDirectories.from subprojects.collect { it.sourceSets.main.allSource.srcDirs }
classDirectories.from subprojects.collect { it.sourceSets.main.output }
executionData.from subprojects.collect { it.tasks.named('test').get().extensions.getByType(JacocoTaskExtension).destinationFile }
reports {
html.required = true
xml.required = true
}
}
7. 自定义 Gradle 插件(独立发布)
独立插件项目结构:
text
my-gradle-plugin/
├── settings.gradle
├── build.gradle
└── src/main/groovy/
└── com/example/MyPlugin.groovy
build.gradle:
groovy
plugins {
id 'groovy-gradle-plugin'
id 'maven-publish'
}
group = 'com.example'
version = '1.0.0'
gradlePlugin {
plugins {
myPlugin {
id = 'com.example.my-plugin'
implementationClass = 'com.example.MyPlugin'
}
}
}
插件实现:
groovy
package com.example
import org.gradle.api.Plugin
import org.gradle.api.Project
class MyPlugin implements Plugin<Project> {
void apply(Project project) {
def extension = project.extensions.create('myConfig', MyExtension)
project.tasks.register('myTask') {
group = 'my-group'
description = 'A custom task from my plugin'
doLast {
println "Running with config: ${extension.message.getOrElse('default')}"
}
}
}
}
Extension:
groovy
package com.example
import org.gradle.api.provider.Property
abstract class MyExtension {
abstract Property<String> getMessage()
}
发布到本地:
bash
gradle publishToMavenLocal
其他项目使用:
groovy
// settings.gradle
pluginManagement {
repositories {
mavenLocal()
gradlePluginPortal()
}
}
// build.gradle
plugins {
id 'com.example.my-plugin' version '1.0.0'
}
myConfig {
message = 'Hello from custom plugin!'
}
8. Gradle TestKit(测试自定义插件)
TestKit 允许用 JUnit 测试 Gradle 插件的行为:
groovy
dependencies {
testImplementation gradleTestKit()
testImplementation 'org.junit.jupiter:junit-jupiter:5.10.2'
}
test {
useJUnitPlatform()
}
测试插件:
java
import org.gradle.testkit.runner.GradleRunner;
import org.gradle.testkit.runner.BuildResult;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import static org.gradle.testkit.runner.TaskOutcome.SUCCESS;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
class MyPluginTest {
@TempDir
File testProjectDir;
@Test
void myTaskShouldSucceed() throws IOException {
// 创建临时项目
Files.writeString(testProjectDir.toPath().resolve("settings.gradle"),
"rootProject.name = 'test-project'");
Files.writeString(testProjectDir.toPath().resolve("build.gradle"), """
plugins {
id 'com.example.my-plugin'
}
myConfig {
message = 'TestKit works!'
}
""");
// 执行 task
BuildResult result = GradleRunner.create()
.withProjectDir(testProjectDir)
.withArguments("myTask")
.withPluginClasspath() // 自动加载当前插件
.build();
// 断言
assertEquals(SUCCESS, result.task(":myTask").getOutcome());
assertTrue(result.getOutput().contains("TestKit works!"));
}
}
9. 制品发布
发布到本地 Maven 仓库
groovy
plugins {
id 'java-library'
id 'maven-publish'
}
publishing {
publications {
mavenJava(MavenPublication) {
from components.java
// 补充 POM 元数据
pom {
name = 'My Library'
description = 'A concise description of my library'
url = 'https://github.com/example/my-library'
licenses {
license {
name = 'The Apache License, Version 2.0'
url = 'https://www.apache.org/licenses/LICENSE-2.0.txt'
}
}
}
}
}
repositories {
maven {
name = 'localBuildRepo'
url = layout.buildDirectory.dir('repo')
}
mavenLocal()
}
}
bash
gradle publish # 发布到所有配置的仓库
gradle publishToMavenLocal # 只发布到本机 ~/.m2
gradle :common:publish # 只发布 common 模块
发布到私服(Nexus / Artifactory)
groovy
publishing {
repositories {
maven {
name = 'nexus'
def isSnapshot = version.toString().endsWith('-SNAPSHOT')
url = isSnapshot
? uri('https://nexus.company.com/repository/maven-snapshots/')
: uri('https://nexus.company.com/repository/maven-releases/')
credentials {
username = System.getenv('NEXUS_USERNAME') ?: ''
password = System.getenv('NEXUS_PASSWORD') ?: ''
}
}
}
}
安全注意:
- 不要把账号密码写进
build.gradle。 - 使用环境变量或 CI Secret。
- 区分 snapshot 和 release 仓库。
发布到 Maven Central(Sonatype)
需要额外步骤:
- 注册 Sonatype 账号,申请
groupId。 - 配置 GPG 签名。
- 使用
signing插件。
groovy
plugins {
id 'signing'
id 'maven-publish'
}
signing {
sign publishing.publications.mavenJava
}
bash
gradle publishMavenJavaPublicationToSonatypeRepository \
-Psigning.keyId=ABCDEF12 \
-Psigning.password=secret \
-Psigning.secretKeyRingFile=~/.gnupg/secring.gpg
10. Android 大型项目治理
Android Gradle 工程常见痛点:
| 问题 | 方向 |
|---|---|
| 模块配置重复 | Convention Plugin |
| 编译慢(KAPT) | 迁移到 KSP |
| Variant 太多 | 控制 flavor 维度,减少无效变体 |
| 依赖冲突 | Version Catalog + dependencyInsight |
| CI 慢 | 分层流水线、缓存 Gradle User Home |
| R8/ProGuard 问题 | 建立 release 构建验证流程 |
KSP 替代 KAPT(显著提升编译速度):
groovy
// 原 KAPT
plugins { id 'kotlin-kapt' }
dependencies { kapt 'com.google.dagger:dagger-compiler:2.51' }
// 改为 KSP
plugins { id 'com.google.devtools.ksp' version '2.0.0-1.0.24' }
dependencies { ksp 'com.google.dagger:dagger-compiler:2.51' }
版本兼容矩阵(升级前必须确认):
| 组件 | 当前版本 | 说明 |
|---|---|---|
| Gradle | 8.x | 见 AGP 兼容表 |
| Android Gradle Plugin | 8.x | 和 Gradle 版本强绑定 |
| Kotlin | 2.x | 和 KSP 版本对齐 |
| JDK | 17 或 21 | AGP 8.x 要求 JDK 17+ |
11. 故障排查方法
依赖无法下载
bash
gradle build --refresh-dependencies --stacktrace --info
排查:
- 仓库地址是否正确。
- 依赖坐标是否存在(去 Maven Central 搜索确认)。
- 网络代理是否生效。
- 是否被公司私服策略拦截。
插件找不到
检查 pluginManagement.repositories:
groovy
pluginManagement {
repositories {
gradlePluginPortal()
mavenCentral()
google() // Android 项目需要
mavenLocal() // 本地开发插件
}
}
构建突然变慢
bash
gradle build --profile
gradle build --scan
gradle help --configuration-cache
重点看:
- 是否新增了配置阶段 IO。
- 是否新增了动态版本依赖。
- 是否缓存失效(
--info看 UP-TO-DATE 判断)。 - 是否 task 输入输出声明缺失。
本地成功,CI 失败
排查步骤:
bash
gradle --version # 确认 Gradle 版本
java -version # 确认 JDK 版本
git status # 确认没有未提交文件
gradle clean build --stacktrace
重点检查:
- JDK 版本(本地 vs CI)。
- Gradle Wrapper(是否提交)。
- 环境变量(CI 缺少本地设置的变量)。
- 私服凭证(CI 未配置)。
- 文件大小写差异(Linux CI 区分大小写)。
OutOfMemoryError
properties
org.gradle.jvmargs=-Xmx4g -XX:MaxMetaspaceSize=1g -XX:+HeapDumpOnOutOfMemoryError
Android 项目额外在模块 gradle.properties:
properties
android.enableJetifier=false # 如果没有旧版依赖,关闭 Jetifier
12. 专家级检查清单
| 检查项 | 合格标准 |
|---|---|
| Gradle 版本 | 使用 Wrapper 固定 |
| JDK 版本 | 通过 Toolchain 声明,本地和 CI 一致 |
| 仓库声明 | 在 settings.gradle 统一 |
| 依赖版本 | 使用 Version Catalog 或平台管理 |
| 动态版本 | 禁止(无 1.+、latest.release) |
| 子模块配置 | 通过 Convention Plugin 统一 |
| 构建缓存 | 开启并验证有效 |
| 配置缓存 | 定期验证兼容性 |
| 代码质量 | Checkstyle / SpotBugs 接入 check 生命周期 |
| 测试覆盖率 | JaCoCo 接入,有门禁阈值 |
| 发布流程 | 测试通过后发布,凭证不入库 |
| 故障定位 | 能用 dependencies、dependencyInsight、--scan、--stacktrace |
13. 实操:发布 demo 的 common 模块
bash
cd demo/gradle-multi-module-demo
# 发布到本地构建目录
gradle :common:publish
# 验证产物
find common/build/repo -type f
应该能看到:
text
common/build/repo/com/example/gradle-demo-common/1.0.0/
├── gradle-demo-common-1.0.0.jar
├── gradle-demo-common-1.0.0-sources.jar
├── gradle-demo-common-1.0.0.module
└── gradle-demo-common-1.0.0.pom
查看 JaCoCo 覆盖率报告:
bash
gradle :common:test :common:jacocoTestReport
open common/build/reports/jacoco/test/html/index.html
14. 面试和工程表达
问:Gradle 为什么比传统构建工具更适合大型项目
完整回答:
Gradle 围绕大型工程提供了完整能力:多模块构建、插件扩展、增量构建、构建缓存、配置缓存、依赖解析、任务图、发布体系和 CI 集成。大型项目的核心问题是可维护性 和性能。Gradle 可以通过 Convention Plugin 统一规范,通过 Version Catalog 治理依赖,通过缓存和懒配置优化性能,通过 Toolchain 和 Wrapper 保证可重复性。
追问点:
- 如何避免 Gradle 脚本越来越乱。
- 如何定位构建慢。
- 如何统一多模块依赖版本。
问:你如何治理一个构建很慢的 Gradle 项目
完整回答:
先用 --profile 或 Build Scan 获取耗时事实,区分是配置阶段慢、依赖解析慢,还是 task 执行慢。然后按顺序处理:固定版本和 Wrapper,开启并行构建和构建缓存,清理动态依赖,减少子模块重复配置,抽取 Convention Plugin,验证配置缓存,对自定义 task 补充输入输出声明。不会一开始凭感觉重构构建脚本。
问:api 和 implementation 怎么选
完整回答:
如果依赖类型出现在模块对外公开的 API 中(public 方法参数、返回值、父类、接口),就用 api。如果依赖只在模块内部实现中使用,就用 implementation。默认优先使用 implementation,因为它能减少下游编译 classpath,降低模块耦合,提高增量编译效率。
问:如何保证构建的可重复性
完整回答:
可重复性需要在多个层面锁定:
- Gradle 版本:通过 Wrapper 固定。
- JDK 版本:通过 Toolchain 声明。
- 依赖版本:通过 Version Catalog 集中管理,禁止动态版本,必要时开启 Dependency Locking。
- 依赖来源 :在
settings.gradle统一仓库,企业内通过私服代理外部仓库。 - 构建环境 :CI 使用固定镜像,用
--no-daemon避免 Daemon 状态污染。