2. 多模块项目的目录组织规范

好,咱们直接切入正题。

Gradle的脚本文件,说白了就是Groovy代码。你写build.gradle的时候,其实就是在写Groovy。很多人一开始觉得Gradle难,我告诉你,难的不是Gradle本身,而是你对Groovy不熟。一旦你掌握了Groovy的基础语法,Gradle的那些配置在你眼里就是一层窗户纸。

我自己带团队的时候,发现很多新人卡在第2周——明明知道要配依赖、要配插件,但就是看不懂脚本里那些花括号和箭头是干嘛的。嗯,今天我们就把这层纸捅破。

1. 变量声明:def 还是 类型?

Groovy是动态语言。你可以写def name = "小明",也可以直接写String name = "小明"。有什么区别?我个人习惯,在Gradle脚本里一律用def。为什么?因为脚本里变量类型没那么重要,写类型反而啰嗦。

// 动态类型
def projectName = "my-app"
def versionCode = 1

// 你也可以指定类型,但一般不这么干
String description = "这是一个示例项目"
小技巧: Groovy中变量默认就是public,不用写public关键字。这点和Java不一样,写起来省事多了。

2. 字符串:单引号、双引号、三引号

字符串是Gradle脚本里出现频率最高的东西。我刚开始用Groovy的时候,常被这三种引号搞晕。来,一次说清楚。

引号类型是否支持插值适用场景
'单引号'否纯字符串,如任务名、常量
"双引号"是(${})需要拼接变量或表达式
'''三单引号'''否多行字符串(如Shell脚本嵌入)
"""三双引号"""是多行模板,比如DSL块内的描述
def name = "Gradle"
def greeting = "Hello, ${name}!"    // 输出: Hello, Gradle!
def multi = """这是第一行
这是第二行
版本号: ${version}"""               // 多行模板
注意: 单引号字符串里(包括三单引号)不能直接用${}插值。曾经有个同事在build.gradle里写'版本号: ${version}',结果输出了字面量${version},排查了半天。

3. 集合:List 和 Map

Gradle里的集合操作比你想象的重要。你要配置依赖、配置插件、配置仓库,背后全是集合操作。先看两个最常用的。

列表(List)

// 定义
def deps = ["spring-core", "spring-beans", "spring-context"]

// 添加元素
deps << "spring-aop"         // 左移操作符,很Groovy
deps.add("spring-jdbc")      // 也能用Java方式

// 遍历
deps.each { dep ->
    println "依赖: $dep"
}

// 过滤
def webDeps = deps.findAll { it.contains("web") }

字典(Map)

// 定义
def config = [
    group: "com.example",
    version: "1.0.0",
    description: "示例项目"
]

// Groovy里用点号访问键
println config.group        // 输出: com.example

// 也支持下标
println config["version"]   // 输出: 1.0.0

// 动态添加
config.javaVersion = "11"

你看,Groovy的集合操作比Java简洁太多了。尤其是那个左移操作符<<,我特别爱用,写起来就像在“把东西推进去”。

4. 闭包——Groovy的灵魂

闭包是Groovy的精髓,也是Gradle DSL的核心。你写的每一个dependencies { }、android { },其实都是在传闭包。

闭包说白了就是一个代码块,可以赋值给变量,也可以当作参数传递。

// 定义一个简单的闭包
def myClosure = { String name ->
    println "你好, $name!"
}

// 调用
myClosure("Groovy")      // 输出: 你好, Groovy!

// 如果只有一个参数,可以用it
def printIt = { println "参数是: $it" }
printIt(42)              // 输出: 参数是: 42

闭包的另一个重要特性:它可以访问外部变量。这个特性在Gradle脚本里特别有用——你可以在闭包里直接使用脚本里定义的变量。

def projectName = "my-module"

def printName = {
    println "当前项目: $projectName"   // 访问外部变量
}

printName()   // 输出: 当前项目: my-module
核心理解: 在Gradle里,dependencies { implementation 'xxx' } 中的dependencies是一个方法,它接收一个闭包作为参数。闭包内部调用的implementation又是另一个方法——这就是DSL的嵌套结构。

5. DSL风格——Gradle脚本为什么可以这样写?

很多人第一次看到build.gradle会觉得“这不是JSON也不是XML,它到底是什么语法?”

答案就是:Groovy的DSL(领域特定语言)风格。说白了就是利用闭包、方法调用省略括号、属性赋值省略等号等特性,让代码看起来像配置文件。

// 常规Java写法
project.configure(project.dependencies, new Action<DependencyHandler>() {
    void execute(DependencyHandler deps) {
        deps.add("implementation", "com.google.guava:guava:30.1-jre");
    }
});

// Gradle DSL写法——省略括号、闭包作为参数
dependencies {
    implementation 'com.google.guava:guava:30.1-jre'
}

你仔细看:dependencies后面没有括号,直接跟了一个花括号闭包。闭包内部又调用了implementation方法,同样省略了括号。这是Groovy允许的——如果你调用方法时,最后一个参数是闭包或者Map,就可以把括号省掉。

还有,属性赋值也可以省略等号:

// 正常写法
project.setGroup("com.example")

// DSL写法
group = "com.example"   // 实际上调用了setGroup()

// 在build.gradle里直接写
group "com.example"     // 也可以,因为Groovy把方法调用当成属性设置

这种灵活性一开始可能让人困惑,但用熟了你会觉得很自然。我在项目里经常看到新人写group = "com.example",其实两种写法都可以,我建议统一用一种风格,团队里别混用。

本章核心知识图谱

下面这张图帮你把Groovy语法和Gradle的关系理清楚。看一遍,你就明白这些零散知识点是怎么串起来的。

Groovy语法与Gradle DSL对应关系 Groovy核心语法 变量与字符串 集合(List/Map) 闭包(Closure) DSL风格 build.gradle中的插值 systemProp.${propName} 依赖集合管理 dependencies { ... } 脚本块与配置闭包 android { defaultConfig { ... } } 方法省略括号 implementation 'xxx' 核心:Gradle脚本 = Groovy语法 + DSL约定 掌握闭包和DSL风格,你就看懂了90%的build.gradle

小结一下

  • 变量:用def,别想太多。
  • 字符串:双引号能插值,单引号纯文本,多行用三引号。
  • 集合:List用<<添加,Map用点号取值——超级方便。
  • 闭包:Gradle脚本的一切都是闭包。记住it是默认参数,方法最后的闭包可以省括号。
  • DSL风格:本质上就是方法调用+闭包的语法糖,别被它吓到。
当年我踩过的坑: 有一次我在build.gradle里写了一个很大的闭包,里面嵌套了好几个闭包,结果一个变量作用域搞错了,折腾了一下午。记住:闭包内的变量引用是“写时绑定”,而不是“运行时绑定”。如果你在循环里创建闭包要特别小心。更简单的做法:尽量保持闭包短小,别写太长。

好了,Groovy的基础够用了。再往后,你就大胆去看Gradle的构建脚本吧——看到{ }就默念“这是闭包”,看到省略括号的方法调用就默念“这是DSL”。熟练了之后,你甚至会觉得这种写法比纯Java或Kotlin DSL更直观。