迁移指南:从 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 之后,这类问题基本绝迹了。

迁移的核心步骤

迁移不是一蹴而就的。我建议分三步走:

  1. 先改一个模块试试水——别一上来就全改,选一个不重要的子项目练手。
  2. 熟悉 Kotlin 语法——尤其是 lambda 和扩展函数,这是 Kotlin DSL 的基石。
  3. 逐步替换——从 build.gradle 改成 build.gradle.kts,然后调整语法。

嗯,这里要注意:Gradle 官方提供了自动迁移工具,但我不建议完全依赖它。自动生成的代码有时候很丑,而且会漏掉一些细节。

常见陷阱与避坑指南

下面这些坑,我基本都踩过。你如果正在迁移,建议逐条对照。

1. 闭包 vs Lambda

Groovy 里闭包是 { },Kotlin 里 lambda 也是 { },但用法不一样。最典型的区别是:

// Groovy — 闭包可以直接传
android {
    compileSdkVersion 30
}

// Kotlin — lambda 需要显式接收者
android {
    compileSdkVersion = 30
}

你看,Kotlin 里 compileSdkVersion 变成了属性赋值,而不是方法调用。这个细节很容易忽略。

⚠️ 注意:Kotlin DSL 中,很多 Groovy 里的「方法调用」变成了「属性赋值」。比如 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")
}

如果你混用 applyplugins,有时候会出现奇怪的依赖顺序问题。我曾经因为这个问题,折腾了一下午才找到原因。

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 里 truefalse 可以直接用,但 Kotlin 里有些地方需要显式转换。比如:

// Groovy
buildTypes {
    release {
        minifyEnabled true
    }
}

// Kotlin
buildTypes {
    release {
        isMinifyEnabled = true  // 注意 is 前缀
    }
}

这个 isMinifyEnabled 的命名,是 Kotlin 属性命名规范导致的。我第一次写的时候,直接写了 minifyEnabled = true,编译报错,查了半天才发现少了个 is

迁移流程图

下面这张图,是我自己总结的迁移决策流程。你可以对照着走一遍:

开始迁移 是否熟悉 Kotlin 语法? 先学习 Kotlin 基础语法 是否使用自动迁移工具? 手动逐文件迁移 自动生成后手动调整

迁移检查清单

我整理了一个清单,每次迁移完一个文件,可以对照着检查一遍:

检查项 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 里 apiimplementation 的区别,在 Kotlin DSL 里是一样的。但如果你用 compileOnly,要注意 Kotlin DSL 里它变成了 compileOnly("xxx"),而且不能省略括号。我有个同事因为这个问题,编译了三次才通过。

⚠️ 特别注意:如果你在 buildSrc 里定义了自定义插件或扩展,迁移到 Kotlin DSL 后,需要确保 buildSrc 本身也是用 Kotlin 写的。否则会出现类型不匹配的问题。

嗯,总的来说,迁移到 Kotlin DSL 是值得的。虽然初期会有点不适应,但一旦习惯了类型安全和 IDE 补全,你就再也不想回到 Groovy 了。我个人建议,新项目直接上 Kotlin DSL,老项目可以逐步迁移,不用急。

最后说一句:迁移过程中遇到问题,别硬扛。Gradle 官方文档和 Kotlin DSL 的源码注释,其实写得挺清楚的。多看看,能省不少时间。


公众号:蓝海资料掘金营,微信deep3321