Gradle 的 Groovy 语法确实也是很多 Android 开发者的“噩梦”。它属于一种动态语言 DSL (领域特定语言),写起来灵活但读起来晦涩,而且 IDE 的代码提示往往也不太好用。
这也是为什么现在 Google 正在大力推行 KTS (Kotlin DSL) 的原因(用 Kotlin 写 Gradle 脚本,有类型检查和自动补全)。
下面我针对两个问题,用“说人话”的方式拆解一下它们的原理
第一个话题:自定义 APK 名称
这段代码看起来复杂,其实它是 Android Gradle Plugin (AGP) 提供的一套遍历逻辑。
applicationVariants.all { variant ->
// ...
}
原理解析:
-
applicationVariants: 这是 Android 插件提供的一个集合,里面装着所有的“变体”。- 什么是变体?就是
BuildType(Debug/Release) 和Flavor(渠道包) 组合后的产物。比如:debug,release,demoDebug,fullRelease等
- 什么是变体?就是
-
.all { variant -> ... }: 这是 Groovy 的语法,相当于 Java 的forEach循环。它在说:“对于每一个生成的变体,都执行下面的代码” -
variant.outputs.all { ... }: 每个变体最终可能会输出多个文件(虽然绝大多数情况我们只输出一个 APK,但 Android 支持根据 CPU 架构或屏幕密度拆分输出多个 APK) 为了保险起见,这里又做了一层循环:“对于这个变体下的每一个输出文件...” -
outputFileName = "...": 这就是最终的目的。修改这个输出文件的文件名属性
为什么这么写?
因为 Gradle 在构建过程中,APK 的名称是在构建的中途生成的。你不能直接写 outputFileName = "xxx",必须挂载到 variant 的处理流程中,等 Gradle 算出当前是哪个变体后,你才有机会去修改它的名字
总结就是:
“Gradle,请监听构建过程。每当你准备好一个变体(比如 Release 版),就去把那个变体即将生成的 APK 改个名字,名字格式我定”
第二个话题:统一版本管理 (config.gradle)
1. 为什么 config.gradle 能被引用?
在 build.gradle (Root) 中:
apply from: 'config.gradle'
这句话的意思是:把 config.gradle 里的代码,原封不动地拿过来,在这个位置执行一遍
注意:通常的做法是直接放在文件的最外层 而不是放在 buildscript { ... } 里面。如果放在 buildscript 里还能生效,可能是因为 Groovy 的作用域穿透或者 Gradle 版本的特性,但标准做法是放在根目录 build.gradle 的第一行或 buildscript 闭包之后
2. ext { ... } 是什么?
ext 是 Gradle 的一个特殊属性,全称叫 ExtraPropertiesExtension。可以把它想象成一个 Map (键值对字典)
所有的 Gradle 对象(Project, Task 等)都有这个 ext
在 config.gradle 里写:
ext {
versionCode = 123
}
因为这个脚本是应用在 Root Project 上的,所以相当于给 根工程对象 挂载了一个全局变量 rootProject.ext.versionCode = 123。
3. rootProject.versionCode 原理
在 App Module (app/build.gradle) 中:
versionCode rootProject.versionCode
这里的逻辑链条是这样的:
rootProject:指代你的根工程对象(就是最外层的那个).versionCode:在 Java/Kotlin 中,并没有versionCode这个字段。但是 Groovy 是动态语言,当它在rootProject对象里找不到versionCode属性时,它会自动去ext里面找- 所以
rootProject.versionCode其实是rootProject.ext.versionCode的缩写(语法糖)
- 所以
这里 rootProject 就代表 config.gradle 的 ext { 了是吗?
不完全是,但效果一样
rootProject代表根工程对象config.gradle里的内容通过apply from被注入到了根工程对象里的ext属性中- 所以你是通过根工程对象,访问到了被注入进去的变量
为什么验证成功但你不确定?
比如定义的那几个方法:
def getVersionCode(build){ ... }
这些定义在 config.gradle 里的方法,因为 apply from 的作用,变成了根工程脚本的一部分。当调用 ext.versionCode = getVersionCode(...) 时,它只是在脚本执行期间计算出了一个整数,并赋值给了 versionCode 变量。子模块拿到的是计算好的结果(那个整数),而不是那个函数
总结与建议
Gradle 的 Groovy 语法确实很“乱”,因为它不仅是配置,还是脚本代码,混杂了声明式(配置)和命令式(逻辑)的代码
建议:
-
关于 config.gradle 的引用位置: 建议将
apply from: 'config.gradle'移出buildscript {}块,直接放在根目录build.gradle的顶部 这样逻辑更符合官方规范:// 根目录 build.gradle apply from: 'config.gradle' // 放在最顶层 buildscript { // ... } -
拥抱 Kotlin DSL (KTS): 如果觉得 Groovy 很难懂,如果是新项目,强烈建议尝试 Kotlin DSL (
build.gradle.kts)。- 优点:你写的
android { ... }都可以点进去看源码,知道里面有什么属性,需要什么类型,IDE 会智能提示。 - Version Catalog:现在 Android 官方推荐用
libs.versions.toml文件来管理版本号,比config.gradle这种方式更清晰、更规范,Android Studio 对它的支持也更好。
- 优点:你写的
目前的写法虽然“乱”,但在这个项目中是奏效且成熟的方案(国内很多旧项目都这么干),可以放心地继续使用