2、Gradle基础入门:Groovy语法速览、Gradle Wrapper原理、settings.gradle和build.gradle文件结构、Project和Task模型
好,咱们正式开始啃 Gradle 这块硬骨头。说实话,我刚接触 Android 构建时,也被 Gradle 那一堆 .gradle 文件搞得晕头转向。但后来我发现,只要抓住几个核心概念,这东西其实挺简单的。今天我们就从最基础的东西说起。
2.1 Groovy 语法速览:你只需要知道这些
Gradle 的构建脚本是用 Groovy 写的。你不需要成为 Groovy 专家,但看懂和写简单的脚本是必须的。我挑几个最常用的点说说。
2.1.1 闭包(Closure)—— Gradle 的灵魂
闭包说白了就是一段可执行的代码块。在 Gradle 里你天天见它:
// 这是一个闭包
android {
compileSdk 34
}
这里的 { } 就是一个闭包,它被传递给 android 方法。闭包可以访问外部变量,也可以接收参数。我个人习惯把闭包理解为「可以到处传递的函数」。
2.1.2 省略括号—— Groovy 的语法糖
Groovy 允许你省略方法调用的括号。所以下面两种写法完全等价:
// 带括号
println("Hello Gradle")
// 省略括号
println "Hello Gradle"
在 build.gradle 里,你看到的 dependencies { } 其实就是 dependencies({ }) 的简写。嗯,这里要注意:省略括号虽然爽,但嵌套多了容易看花眼。
2.1.3 字符串与 GString
单引号和双引号有区别。双引号支持插值:
def name = "Gradle"
println "Hello, $name" // 输出:Hello, Gradle
println 'Hello, $name' // 输出:Hello, $name(原样输出)
我在项目中遇到过有人用单引号写路径,结果变量没展开,排查了半天。所以建议:需要变量就用双引号,纯字符串用单引号。
2.1.4 集合与映射
List 和 Map 的写法也很简洁:
// List
def list = [1, 2, 3]
println list[0] // 1
// Map
def map = [key1: 'value1', key2: 'value2']
println map.key1 // value1
你想想看,build.gradle 里的 defaultConfig { } 本质上就是在操作一个 Map。
test.groovy 文件,然后运行 groovy test.groovy。前提是你装了 Groovy SDK。
2.2 Gradle Wrapper 原理:为什么你的项目不需要装 Gradle?
你有没有好奇过:为什么从 GitHub 拉下一个 Android 项目,不用装 Gradle 就能直接编译?答案就是 Gradle Wrapper。
Wrapper 本质上是一个脚本(gradlew + gradlew.bat)和一个配置文件(gradle-wrapper.properties)。它的工作流程很简单:
- 检查本地有没有指定版本的 Gradle
- 如果没有,自动从配置的地址下载
- 用下载好的 Gradle 执行构建
我曾经遇到过团队里有人直接装了个全局 Gradle 8.0,但项目要求 7.4,结果构建失败。后来强制大家用 Wrapper,再也没出过这种问题。
核心配置文件长这样:
# gradle-wrapper.properties
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
这里最关键的就是 distributionUrl,它决定了用哪个 Gradle 版本。我建议你每次升级 Gradle 时,只改这个 URL,然后重新生成 Wrapper 文件。
gradle-wrapper.properties 加入 .gitignore。这个文件必须提交到版本控制,否则别人拉下来不知道用哪个版本。
2.3 settings.gradle 和 build.gradle 文件结构
一个 Android 项目通常有多个 .gradle 文件。新手最容易搞混的就是它们各自的作用。我画了张图帮你理清关系:
2.3.1 settings.gradle:告诉 Gradle 有哪些模块
这个文件在项目根目录。它的核心作用就是声明项目包含哪些子模块:
// settings.gradle
pluginManagement {
repositories {
google()
mavenCentral()
gradlePluginPortal()
}
}
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
google()
mavenCentral()
}
}
rootProject.name = "MyApp"
include ':app'
include ':library'
注意 include ':app' 这一行。它告诉 Gradle:「嘿,我要把 app 这个文件夹当作一个模块来构建」。如果你新增了一个模块,记得在这里加上。
2.3.2 根 build.gradle:全局配置
根目录的 build.gradle 通常只配置两样东西:插件声明和全局属性。
// 根 build.gradle
plugins {
id 'com.android.application' version '8.2.0' apply false
id 'com.android.library' version '8.2.0' apply false
id 'org.jetbrains.kotlin.android' version '1.9.0' apply false
}
apply false 的意思是「先声明这个插件,但别应用」。真正的应用是在子模块的 build.gradle 里。这样做的好处是:所有模块的插件版本统一管理。
2.3.3 模块 build.gradle:具体怎么构建
这才是你真正经常改的文件。它配置了编译 SDK、构建类型、依赖等:
// app/build.gradle
plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
}
android {
namespace 'com.example.myapp'
compileSdk 34
defaultConfig {
applicationId "com.example.myapp"
minSdk 24
targetSdk 34
versionCode 1
versionName "1.0"
}
buildTypes {
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation 'androidx.core:core-ktx:1.12.0'
implementation 'androidx.appcompat:appcompat:1.6.1'
}
我在项目中遇到过有人把 compileSdk 和 targetSdk 搞混。简单说:compileSdk 决定你能用哪些 API,targetSdk 决定你的 App 在哪个系统版本上表现最优。
2.4 Project 和 Task 模型:Gradle 构建的基本单元
Gradle 的构建过程,说白了就是「在 Project 上执行一系列 Task」。每个 build.gradle 文件对应一个 Project,而 Project 里包含多个 Task。
2.4.1 Project:构建的容器
每个模块都是一个 Project。根目录是一个 Project,app 模块也是一个 Project。Project 提供了:
- 属性:比如
project.name、project.buildDir - 方法:比如
project.dependencies { }、project.task('hello') { } - 生命周期:初始化、配置、执行三个阶段
你写的 android { }、dependencies { } 其实都是在调用 Project 的方法。
2.4.2 Task:构建的最小执行单元
Task 就是一个个具体的构建动作。比如编译 Java 代码是一个 Task,生成 APK 是一个 Task,运行单元测试也是一个 Task。
你可以自定义 Task:
// 在 build.gradle 中
task hello {
doLast {
println "Hello, Gradle!"
}
}
然后在终端运行:
./gradlew hello
就会看到输出。你想想看,整个构建过程就是由成百上千个这样的 Task 组成的。
2.4.3 Task 依赖与执行顺序
Task 之间可以有依赖关系。比如 assembleDebug 依赖 compileDebugKotlin,所以会先编译再打包。
task taskA {
doLast { println "执行 A" }
}
task taskB(dependsOn: taskA) {
doLast { println "执行 B" }
}
运行 ./gradlew taskB 会先执行 A,再执行 B。我曾经在优化构建速度时,就是通过分析 Task 依赖图,找到了不必要的串行依赖,改成了并行执行,构建时间直接缩短了 30%。
./gradlew tasks --all 可以列出所有可用的 Task。运行 ./gradlew :app:dependencies 可以查看依赖树。这两个命令我几乎天天用。
2.5 本章小结
这一章我们覆盖了 Gradle 构建脚本的基础。Groovy 语法你不需要背,写多了自然就熟了。Wrapper 是团队协作的基石,一定要用。三个 .gradle 文件各司其职,别搞混。Project 和 Task 模型是理解 Gradle 的关键,记住「构建就是执行 Task」就够了。
下一章我们会深入 Gradle 的生命周期和配置阶段,到时候你会更清楚「为什么 build.gradle 里的代码有时候执行了,有时候没执行」。嗯,先消化这些吧。