迁移指南:从 Groovy DSL 迁移到 Kotlin DSL、常见陷阱
说实话,我从 Groovy DSL 切到 Kotlin DSL 的时候,心里是有点抵触的。毕竟 Groovy 用了好几年,闭包写得飞起,突然要换一套语法,总觉得没必要。但后来项目越来越大,类型安全带来的好处就体现出来了——编译期就能发现拼写错误,IDE 补全也准了很多。今天我就把迁移过程中踩过的坑、总结的经验,一次性说清楚。
为什么值得迁移?
先说说我个人的感受。Groovy DSL 最大的问题是「太灵活了」。你想想看,一个字符串写错了,它不会报错,直到运行时才炸。Kotlin DSL 就不一样,它是类型安全的——说白了,你写错了,编译器直接告诉你。
举个例子:
// Groovy DSL — 运行时才报错
dependencies {
implementation 'com.example:lib:1.0' // 拼错了?不知道
}
// Kotlin DSL — 编译期检查
dependencies {
implementation("com.example:lib:1.0") // 拼错了?IDE 直接标红
}
我在项目中遇到过好几次,因为 Groovy 脚本里少写了一个冒号,导致 CI 构建失败,排查了半天。换成 Kotlin DSL 之后,这类问题基本绝迹了。
迁移的核心步骤
迁移不是一蹴而就的。我建议分三步走:
- 先改一个模块试试水——别一上来就全改,选一个不重要的子项目练手。
- 熟悉 Kotlin 语法——尤其是 lambda 和扩展函数,这是 Kotlin DSL 的基石。
- 逐步替换——从
build.gradle改成build.gradle.kts,然后调整语法。
嗯,这里要注意:Gradle 官方提供了自动迁移工具,但我不建议完全依赖它。自动生成的代码有时候很丑,而且会漏掉一些细节。
常见陷阱与避坑指南
下面这些坑,我基本都踩过。你如果正在迁移,建议逐条对照。
1. 闭包 vs Lambda
Groovy 里闭包是 { },Kotlin 里 lambda 也是 { },但用法不一样。最典型的区别是:
// Groovy — 闭包可以直接传
android {
compileSdkVersion 30
}
// Kotlin — lambda 需要显式接收者
android {
compileSdkVersion = 30
}
你看,Kotlin 里 compileSdkVersion 变成了属性赋值,而不是方法调用。这个细节很容易忽略。
minSdkVersion 21 要写成 minSdkVersion = 21。少写等号,编译直接报错。
2. 字符串与引号
Groovy 里字符串可以用单引号、双引号,甚至三引号。Kotlin 里只有双引号和三引号。这个迁移起来倒不难,但容易忘。
// Groovy
implementation 'com.example:lib:1.0'
// Kotlin
implementation("com.example:lib:1.0")
我个人习惯把所有依赖都写成双引号,这样统一,也方便以后加字符串模板。
3. 插件声明方式
这个坑我印象很深。Groovy 里插件可以这样写:
apply plugin: 'com.android.application'
但在 Kotlin DSL 里,推荐用 plugins 块:
plugins {
id("com.android.application")
}
如果你混用 apply 和 plugins,有时候会出现奇怪的依赖顺序问题。我曾经因为这个问题,折腾了一下午才找到原因。
4. 配置块中的 this
Groovy 里闭包中的 this 指向闭包本身,而 Kotlin 里 lambda 的 this 指向接收者。这个区别会导致一些隐式调用失效。
// Groovy — 可以直接调用方法
android {
compileSdkVersion 30
defaultConfig {
applicationId "com.example.app"
}
}
// Kotlin — 需要显式指定接收者
android {
compileSdkVersion = 30
defaultConfig {
applicationId = "com.example.app"
}
}
嗯,这里要注意:Kotlin DSL 里,defaultConfig 是一个函数,它接收一个 lambda,lambda 的接收者是 DefaultConfig 类型。所以里面的 applicationId 是属性赋值,不是方法调用。
5. 布尔值与条件判断
Groovy 里 true 和 false 可以直接用,但 Kotlin 里有些地方需要显式转换。比如:
// Groovy
buildTypes {
release {
minifyEnabled true
}
}
// Kotlin
buildTypes {
release {
isMinifyEnabled = true // 注意 is 前缀
}
}
这个 isMinifyEnabled 的命名,是 Kotlin 属性命名规范导致的。我第一次写的时候,直接写了 minifyEnabled = true,编译报错,查了半天才发现少了个 is。
迁移流程图
下面这张图,是我自己总结的迁移决策流程。你可以对照着走一遍:
迁移检查清单
我整理了一个清单,每次迁移完一个文件,可以对照着检查一遍:
| 检查项 | Groovy 写法 | Kotlin 写法 | 常见错误 |
|---|---|---|---|
| 插件声明 | apply plugin: 'xxx' |
plugins { id("xxx") } |
混用两种方式 |
| 依赖声明 | implementation 'xxx' |
implementation("xxx") |
漏掉括号或引号 |
| 属性赋值 | minSdkVersion 21 |
minSdkVersion = 21 |
忘记写等号 |
| 布尔属性 | minifyEnabled true |
isMinifyEnabled = true |
漏掉 is 前缀 |
| 配置块 | android { ... } |
android { ... } |
内部语法不一致 |
避坑指南:我曾经踩过的雷
最后分享几个我亲身经历过的坑,希望能帮你省点时间。
坑一:Gradle 版本不兼容
我曾经把项目从 Gradle 6.8 迁移到 7.0,结果 Kotlin DSL 脚本里的一些 API 变了。比如 extra 属性的访问方式,在 7.0 里需要加 by extra。所以迁移前,一定要先确认 Gradle 版本和 Kotlin DSL 的兼容性。
技巧:迁移时,建议先升级 Gradle 到最新稳定版,再改脚本。这样能避免「版本不兼容」和「语法迁移」两个问题混在一起。
坑二:多模块项目中的依赖传递
Groovy 里 api 和 implementation 的区别,在 Kotlin DSL 里是一样的。但如果你用 compileOnly,要注意 Kotlin DSL 里它变成了 compileOnly("xxx"),而且不能省略括号。我有个同事因为这个问题,编译了三次才通过。
buildSrc 里定义了自定义插件或扩展,迁移到 Kotlin DSL 后,需要确保 buildSrc 本身也是用 Kotlin 写的。否则会出现类型不匹配的问题。
嗯,总的来说,迁移到 Kotlin DSL 是值得的。虽然初期会有点不适应,但一旦习惯了类型安全和 IDE 补全,你就再也不想回到 Groovy 了。我个人建议,新项目直接上 Kotlin DSL,老项目可以逐步迁移,不用急。
最后说一句:迁移过程中遇到问题,别硬扛。Gradle 官方文档和 Kotlin DSL 的源码注释,其实写得挺清楚的。多看看,能省不少时间。
公众号:蓝海资料掘金营,微信deep3321