第3章:构建脚本语法变化:Groovy vs Kotlin DSL迁移
从 Groovy 转向 Kotlin DSL,坦白讲是 Gradle 这些年最「折腾」人的变化之一。我刚开始升级项目时,也犯过不少低级错误,比如漏了个括号、类型没写对,导致构建直接崩掉。后来整理出一套迁移 checklist,分享给你。
3.1 为什么官方要推 Kotlin DSL?
说白了就两个原因:类型安全和 IDE 补全。Groovy 是动态语言,你在 build.gradle 里写个错别字,只有运行到那一行才知道。Kotlin DSL 在编辑时就帮你检查,我个人的感受——迁移后,配置类错误的调试时间至少省了一半。
核心变化一览表:
| 对比项 | Groovy | Kotlin DSL |
|---|---|---|
| 文件后缀 | build.gradle | build.gradle.kts |
| 字符串 | 单引号、双引号均可 | 双引号必需 |
| 闭包 | 隐式闭包 ( { } ) | Lambda 表达式 ( { -> } ) |
| 属性赋值 | ext.myVar = "value" | val myVar by extra("value") |
| 插件声明 | apply plugin: 'com.android.application' | plugins { id("com.android.application") } |
3.2 迁移时最容易踩的坑
3.2.1 括号和引号——最阴间的报错
我记得第一次把 build.gradle 改名为 .kts 后,IDE 直接一片红。原因是 Groovy 里很多地方省略了括号,比如:
// Groovy — 能跑
implementation 'com.android.support:appcompat-v7:28.0.0'
// Kotlin DSL — 必须写括号
implementation("com.android.support:appcompat-v7:28.0.0")
为什么不能省?因为 Kotlin 的函数调用必须加括号。还有 lambda 的写法:
// Groovy
android {
compileSdkVersion 30
}
// Kotlin DSL — 要注意 it 的使用
android {
compileSdkVersion = 30 // 注意: compileSdkVersion 是 var,需要 = 赋值
}
我自己的经验: 把所有 dependencies 块里的字符串改成函数调用形式,花了我将近一小时。建议用正则批量替换,效率高很多。
3.2.2 闭包变成 Lambda,it 隐式参数不见了
Groovy 的闭包自带一个隐式参数 it,但在 Kotlin DSL 里,必须显式声明。
// Groovy — 直接能用 it
tasks.whenTaskAdded { task ->
if (task.name == "assembleDebug") {
// do something
}
}
// Kotlin DSL — it 需要声明
tasks.whenTaskAdded {
if (this.name == "assembleDebug") { ... } // 注意: this 就是 task
}
it 被替换成 this 或显式变量名。3.2.3 extension 属性全部变成 getter/setter
Groovy 里可以直接对 extension 赋值,Kotlin DSL 里你必须通过 setter 或者 val 声明。比如 sourceSets 的写法:
// Groovy
sourceSets {
main {
java.srcDirs = ['src/main/java']
}
}
// Kotlin DSL
sourceSets {
getByName("main") {
java.srcDirs("src/main/java") // 注意不是 =,而是函数调用
}
}
为什么会这样?因为 Kotlin DSL 的 SourceSetContainer 需要调用 getByName 获取指定名称的配置块。我个人建议统一使用 getByName,更清晰,也便于 IDE 跳转。
3.3 迁移三步骤(实战流程)
- 第一步:改文件名 — 把
build.gradle重命名为build.gradle.kts,然后同步。此时 IDE 会报错,别慌。 - 第二步:逐块修正语法 — 按照报错从少到多修,先修
plugins块(最简单),再修android块,最后修dependencies和task。 - 第三步:验证构建 — 执行
./gradlew assembleDebug看能否通过。如果报错,优先看.kts文件里的红色波浪线。
💡 小技巧: 我通常在迁移时保留一份原 Groovy 文件在另一个目录,方便对照。另外,把报错信息直接复制到 IDE 的“Problems”面板里,双击能跳转到具体行,效率翻倍。
3.4 Kotlin DSL 下常见的崩溃复现场景
| 错误现象 | 根因 | 修复示例 |
|---|---|---|
| “Unresolved reference: implementation” | dependencies 块没用括号 | implementation("com.example") |
| “Type mismatch: inferred type is Int but Unit was expected” | 赋值时用了 = 但实际是函数调用 |
检查是否应该用 compileSdkVersion(30) 而不是 = 30 |
| “Cannot infer type for T” | lambda 参数未显式声明 | 给 lambda 加上显式参数类型 |
| “This declaration is deprecated” | 用了旧版 API(如 ext 属性) | 迁移到 by extra 方式 |
你看,迁移本质上就是语法的机械转换,但坑在细节。我团队曾经在合并代码时,有人只改了主模块的 .gradle,忘了改 library 模块,结果 CI 跑了一小时才崩——教训就是统一改完再提交,别拖。Kotlin DSL 虽然初期有点绕,但适应之后,写脚本就跟写业务代码一样舒服了。