将 Android Gradle 文件 从 Groovy DSL 迁移到 Kotlin DSL 的百科全书
自从 Android Studio Giraffe, Kotlin DSL 便成为了 Android 开发中 Gradle 脚本的新标准. 当你使用集成开发环境的内置模板创建新项目时, 你将被提供 Kotlin DSL 文件, 而不是基于 Groovy 的 Gradle 文件.
这一即将到来的变化给了我信心, 让我最终迈出了将基于 Groovy DSL 的 Gradle 配置迁移到 Kotlin DSL 的一步. 在本文中, 我将带领大家了解我必须采取的步骤.
本文旨在成为一本百科全书和指南, 在你迁移到 Kotlin DSL 的过程中为你提供帮助. 虽然本文主要针对 Android 项目, 但讨论的要点同样适用于其他基于 Gradle 的项目, 例如 Spring Boot 应用.
在第一部分, 我将简要介绍从 Groovy 迁移到 Kotlin DSL 的一般过程. 在接下来的章节中, 我将列出迁移过程中必须经历的所有环节.
迁移过程
单模块 Android 项目的常规结构在大多数情况下都非常相似. 当然, 项目可能会使用一些额外的 Gradle 配置文件, 但总的来说, 迁移过程基本相同.
多模块项目也是如此, 每个模块都有单独的build.gradle
.
常规单模块 Android 项目 Gradle 文件结构.
从这个结构中, 我们可以找出三个要转换为 Kotlin DSL 的基于 groovy 的 Gradle 文件.
- settings.gradle:
settings.gradle
文件负责在Android项目中配置和定义项目的模块结构. 它位于项目的根目录中, 在组织和管理构建过程中起着至关重要的作用. - 项目级 build.gradle: 项目级
build.gradle
文件也位于根目录中, 负责为整个项目的构建过程设置配置. 它定义了适用于项目中所有模块的全局设置, 资源库和依赖关系. - 模块(应用)级 build.gradle: Android 项目中的每个模块(如应用模块)中都有一个"模块级"
build.gradle
文件. 该文件负责配置特定模块的特定构建设置, 依赖关系和行为.
从这个 Gradle 文件结构来看, 对我来说最合理的迁移结构如下:
- 将
settings.gradle
文件迁移到settings.gradle.kts
文件中 - 迁移所有外部 gradle 文件, 例如将
dependencies.gradle
文件迁移到包含依赖版本名称的dependencies.gradle.kts
文件. - 将项目级的
build.gradle
文件迁移到build.gradle.kts
文件 - 将模块级的
build.gradle
文件迁移到build.gradle.kts
文件.
用这种方式你将会遇到最少的冲突. 此外, 在每个迁移步骤中, 如果 Kotlin DSL 因为尚未成功构建而尚未应用, 注释掉这些部分通常会有所帮助. 一旦用转换后的文件构建了项目, 你就会得到集成开发环境的支持, 调试问题也会比猜测 Groovy 属性在 Kotlin DSL 中的名称容易得多.
迁移百科
正如最开始的时候解释的那样, 大多数项目结构都是相同的. 即使是在复杂的 Gradle 设置中, 在迁移过程中也会面临同样的挑战.
在下文中, 你可以找到我偶然发现的问题以及解决方法. 在每个小节中, 我都会解释 Gradle Groovy 代码段的用途, 以及迁移后的 Kotlin DSL 代码是什么样的.
如果你发现有任何遗漏的部分可能与更多读者相关, 请随时评论, 我会把它们添加进来中.
用双引号替换单引号
在使用 Kotlin DSL 时, 我们需要确保在字符串中使用双引号(")而不是单引号('). 因此, 请查看你的文件并查找单引号.
你可以使用 IntelliJ 查找函数 (Mac ⌘ + F ; Windows Strg + F ) 或直接使用替换函数 (Mac ⌘ + R ; Windows Strg + R) 加快这一过程.
Groovy:
arduino
implementation 'androidx.activity:activity-ktx:1.7.2'
在 Kotlin DSL 中变成:
arduino
implementation "androidx.activity:activity-ktx:1.7.2"
访问另一个 Gradle 文件中的值
要访问另一个 Gradle 文件中的值, 在 Groovy 中可以使用下面的示例:
Groovy:
css
apply from: rootProject.file('dependencies.gradle')
在 Kotlin DSL 中, 它现在变成:
ini
apply(from = rootProject.file("dependencies.gradle"))
apply plugin
如果你在使用apply plugin
语法, 下面是该语法的迁移方法:
Groovy:
arduino
apply plugin: "org.jlleitschuh.gradle.ktlint"
在 Kotlin DSL 中变成:
ini
apply(plugin= "com.android.application")
plugins块
使用插件的新语法是plugins { .. }
块. 在下面的示例中, 你可以看到有和没有设置插件显式版本的声明示例.
Groovy:
bash
plugins {
id 'com.android.application'
id 'com.google.devtools.ksp' version Ƈ.8.21-1.0.11'
..
}
在 Kotlin DSL 中变成:
bash
plugins {
id("com.android.application")
id("com.google.devtools.ksp") version "1.8.20-1.0.10"
..
}
迁移额外变量
如果你的项目使用ext { .. }
块来定义依赖项的版本名称或其他参数, 下面就告诉你如何迁移.
假设你的ext { .. }
块如下所示:
Groovy
ini
ext {
accompanist_version = "0.30.0"
compose_compiler_version = "1.4.7"
..
}
通过 Kotlin DSL 定义版本名称有两种方法.
首先, 通过访问额外的映射
在 Kotlin DSL 中变成:
arduino
extra["accompanist_version"] = "0.30.0"
extra["compose_compiler_version"] = "1.4.7"
或者, 也可以使用by extra
委托.
Kotlin DSL:
kotlin
val accompanist_version by extra("0.30.0")
val compose_compiler_version by extra("1.4.7")
访问额外参数
在 Groovy 中, 你可以通过各自的变量名直接访问 Strings 中的项目额外参数, 而在 Kotlin DSL 中, 我们必须使用委托.
在应用模块build.gradle.kts
中, 你可以以下列方式使用by project
委托访问额外变量:
Kotlin DSL:
kotlin
val accompanist_version: String by project
..
dependencies {
implementation "com.google.accompanist:accompanist-systemuicontroller:$accompanist_version"
}
引入 Gradle 模块
在settings.gradle
中包含 Gradle 模块. 对于单模块项目, 这只是应用模块. 如果迁移的是多模块项目, 则应迁移所有包含的模块.
对于settings.gradle
, 你可以按以下方式转换include
:
Groovy:
php
include ':app'
在 Kotlin DSL 中变成:
php
include(":app")
声明仓库
repositories
块用于定义可以解析依赖关系的仓库. 对于每个仓库, 我们都使用函数调用.
然而, 在 Kotlin DSL 中声明资源库与 Groovy 非常相似. 请看下面的内容:
Groovy:
scss
repositories {
google()
mavenCentral()
gradlePluginPortal()
maven { url "https://plugins.gradle.org/m2/" }
}
在 Kotlin DSL 中变成:
scss
repositories {
google()
mavenCentral()
gradlePluginPortal()
maven(url = "https://plugins.gradle.org/m2/")
}
依赖关系配置
在我们的项目中有多种方法来声明依赖关系. 例如, classpath
用于为我们的构建过程本身声明依赖关系. 它用于项目级build.gradle
文件中的buildscript
块.
另一方面, implementation
配置负责声明在代码中和运行时实际使用的依赖项.
另一个常见的关键字是platform
关键字, 例如, 在通过物料清单(BOM)声明依赖关系时使用, 就像我们从 Firebase 或 Jetpack Compose 中了解到的那样.
在使用 KAPT 或 KSP 等注释处理器时, 我们也会在代码中使用相应的关键字kapt
和ksp
声明相应的依赖关系.
此外, 当包含来自libs
等目录的文件时, 我们可以使用fileTree
关键字.
下面是所有上述关键字的代码片段:
Groovy:
java
// Buildscript dependency
classpath 'com.android.tools.build:gradle:8.0.2'
// Regular dependency
implementation "androidx.room:room-ktx:2.5.1"
// Include .jar files from libs folder
implementation fileTree (dir: "libs", include: ["*.jar"])
// Platform
implementation platform('androidx.compose:compose-bom:2023.05.01')
// Annotation processor
kapt "androidx.room:room-compiler:2.5.1"
// Or
ksp "androidx.room:room-compiler:2.5.1"
在 Kotlin DSL 中变成:
less
// Buildscript dependency
classpath("com.android.tools.build:gradle:8.0.2")
// Regular dependency
implementation("androidx.room:room-ktx:2.5.1")
// Include .jar files from libs folder
implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.jar"))))
// Platform
implementation(platform("androidx.compose:compose-bom:2023.05.01"))
// Annotation processor
kapt("androidx.room:room-compiler:2.5.1")
// Or
ksp("androidx.room:room-compiler:2.5.1")
BuildType 和 Flavor 依赖配置
当你只想将依赖关系分配给特定的构建类型时, 以前使用 Groovy DSL 时, 你可以直接使用构建类型名称并添加implementation
, 就像我们在上一节为常规依赖关系所做的那样.
但在 Kotlin DSL 中, 这不再可能. 取而代之的是, 我们使用带有构建类型或构建类型名称的配置委托, 以及与特定依赖关系的风味组合.
看看下面的例子就知道了:
Groovy:
arduino
dependencies {
..
myCustomBuildTypeImplementation 'com.mydependency:1.0.0'
}
在 Kotlin DSL 中变成:
kotlin
val myCustomBuildType by configurations.creating
dependencies {
..
myCustomBuildType("com.mydependency:1.0.0")
}
任务注册
通过注册任务, 我们可以自定义并控制构建流程. 我们可以实现符合各自要求的自定义代码.
在下面的示例任务中, 我们看到创建了一个任务"clean", 该任务在执行时使用Delete
任务删除各自根项目的构建目录.
与 Groovy 不同, 在 Kotlin DSL 中, 我们使用函数register
来创建新任务.
Groovy:
arduino
task clean (type: Delete) {
delete rootProject.buildDir
}
在 Kotlin DSL 中变成:
javascript
tasks.register("clean", Delete::class) {
delete(rootProject.buildDir)
}
赋值和 Android 默认配置
配置参数时, 在 Groovy 中使用的语法是parameterName value
. 在 Kotlin DSL 中, 如果底层代码提供了可变变量, 我们只需在这两个值之间使用=
操作符即可为参数赋值.
对于 Android 配置和defaultConfig
而言, 生成的脚本可以在下面的示例迁移片段中找到.
Groovy:
arduino
android {
namespace 'com.my.project'
compileSdkVersion 33
defaultConfig {
applicationId "com.my.project"
minSdkVersion 24
targetSdkVersion 33
versionCode 1
versionName "1.0.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
..
}
在 Kotlin DSL 中变成:
ini
android {
namespace = "com.my.project"
compileSdk = 33
defaultConfig {
applicationId = "com.my.project"
minSdk = 28
targetSdk = 33
versionCode = 1
versionName = "1.0.0"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
..
}
kotlinOptions块
Kotlin 选项块的迁移相对简单. 如果你使用freeCompilerArgs
, 你只需稍微调整一下语法.
将+=
替换为:
Groovy:
ini
kotlinOptions {
jvmTarget = "17"
freeCompilerArgs += "-opt-in=kotlin.RequiresOptIn"
}
在 Kotlin DSL 中变成:
ini
kotlinOptions {
jvmTarget = "17"
freeCompilerArgs = freeCompilerArgs + "-Xopt-in=kotlin.RequiresOptIn"
}
打包选项排除
在某些情况下, 你希望从最终构建的应用bundle中排除某些资源. 为此, 你可以使用packagingOptions
为resources
添加排除规则.
Groovy:
css
packagingOptions {
resources {
excludes += ["META-INF/LICENSE.txt", "META-INF/NOTICE.txt"]
}
}
在 Kotlin DSL 中变成:
arduino
packagingOptions {
resources {
excludes += setOf("META-INF/LICENSE.txt", "META-INF/NOTICE.txt")
}
}
添加productFlavors
添加productFlavors是一项方便的功能, 可用于定义应用的特定类型. 常见的productFlavors味是"dev", 你可以在其中为后端服务器设置不同的路由, 或设置与生产构建不同的其他配置.
让我们来看一个定义dev
类型的迁移示例. 注意我们是如何使用.add(..)
函数而不是直接调用flavorDimensions()
函数的, 因为底层值是一个MutableList<String>
.
在创建风味时, 我们现在必须使用create("ourFlavorName")
语法, 而不是在配置块前直接声明名称.
此外, 在添加基础 URL 时, 由于现在必须使用双引号, 我们需要通过"
"转义 URL 字符串内部的双引号.
Groovy:
arduino
flavorDimensions("app")
productFlavors {
dev {
dimension = 'app'
applicationId 'dev.com.my.project'
buildConfigField 'String', 'BASE_URL', '"https://api.yourdevserver.com"'
buildConfigField 'Boolean', 'ANALYTICS_ENABLED', 'true'
}
}
在 Kotlin DSL 中变成:
javascript
flavorDimensions.add("app")
productFlavors {
create("dev") {
dimension = "app"
applicationId = "dev.com.my.project"
buildConfigField("String", "BASE_URL", ""https://api.yourdevserver.com"")
buildConfigField("Boolean", "ANALYTICS_ENABLED", "true")
}
}
签名配置
要动态创建签名配置, 我们可以使用signingConfigs
块.
迁移与添加新productFlavors的迁移类似. 我们现在使用create(..)
任务, 而不是直接在配置块前面命名相应的配置名称:
Groovy:
lua
signingConfigs {
release {
storeFile file("pathToOurKeystore")
storePassword System.getenv ("STORE_PASSWORD")
keyAlias System.getenv("KEY_ALIAS")
keyPassword System.getenv("KEY_PASSWORD")
}
}
在 Kotlin DSL 中变成:
ini
signingConfigs {
create("release") {
storeFile = file("pathToOurKeystore")
storePassword = System.getenv("STORE_PASSWORD")
keyAlias = System.getenv("KEY_ALIAS")
keyPassword = System.getenv("KEY_PASSWORD")
}
}
定义构建类型
在 Gradle 中, buildTypes
块可用于为 Android 项目定义不同的构建配置. 每种构建类型都代表应用的一个特定变体, 如debug
或release
, 都有自己的一组配置选项.
通过buildTypes
块, 你可以自定义构建的各个方面, 如启用或禁用调试, 启用代码和资源缩减, 指定 ProGuard 规则, 自定义输出文件名以及为签名配置赋值.
要使用 Kotlin DSL 创建新的buildType
, 就像创建 flavors
和 signingConfigs
一样, 我们可以使用create(...)
. 不过, 在配置默认构建类型release
和debug
时, 我们需要使用getByName(..)
代替.
boolean
类型的配置参数也会稍有变化, 例如从debuggable
变为isDebuggable
.
此外, 在这种情况下, 有时你想通过遍历 applicationVariants
和更新outputFileName
变量来调整输出文件的文件名.
Kotlin DSL 的情况则有些不同. 请看下面的代码段, 了解迁移示例:
Groovy:
arduino
buildTypes {
release {
debuggable false
minifyEnabled true
shrinkResources true
proguardFiles getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro"
android.applicationVariants.all { variant ->
variant.outputs.all { output ->
outputFileName = "${variant.name}-${variant.versionName}-${versionCode}.aab"
}
}
signingConfig signingConfigs.release
}
myCustomBuildType {
..
}
}
在 Kotlin DSL 中变成:
kotlin
// This line goes to the top of your build.gradle.kts file
import com.android.build.gradle.internal.api.BaseVariantOutputImpl
..
buildTypes {
getByName("release") {
isDebuggable = false
isMinifyEnabled = true
isShrinkResources = true
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
android.applicationVariants.all {
outputs.all {
val output: BaseVariantOutputImpl = (this as BaseVariantOutputImpl)
output.outputFileName = "${name}-${versionName}-${versionCode}.aab"
}
}
signingConfig = signingConfigs.getByName("release")
}
create("myCustomBuildType) {
..
}
}
配置testOptions
为了调整单元测试的配置, 我们可以使用包含在testOptions
块中的unitTests
块.
在这里, 我们只需将includeAndroidResources
更改为isIncludeAndroidResources
即可, 如下代码片段所示:
Groovy
ini
testOptions {
unitTests {
includeAndroidResources = true
}
}
在 Kotlin DSL 中变成:
ini
testOptions {
unitTests {
isIncludeAndroidResources = true
}
}
总结一下
将 Gradle 文件从 Groovy 转换为 Kotlin DSL 可以显著改善 Android 开发工作流程. 如果你已经熟悉 Kotlin, 效果会更好. 转用熟悉的单一语言不仅能提高你的工作效率, 还能避免在两种语言之间跳来跳去地完成开发和配置任务. Kotlin DSL 的健壮性和直观性让你有信心创建自定义的 Gradle 任务, 而不必使用 Groovy 经常出现的模糊语法.
尽管 Groovy 在 Gradle 脚本配置中占据主导地位, 而且它与 Java 相似, 许多 Android 或 JVM 开发人员一般都应该熟悉 Java, 但很少有开发人员完全学会 Groovy. 很多时候, Gradle 脚本依赖于从相关框架文档, StackOverflow 答案或其他地方复制粘贴的片段, 而这些片段可能并不总是最佳解决方案. 因此, 切换到 Kotlin DSL 不仅是一个可操作的改变, 也是提高代码可维护性和可读性的一个步骤.
随着 Android 开发生态系统的发展, 我们的方法也应随之发展. 本指南基于我的迁移过程, 旨在涵盖你在迁移过程中可能遇到的各种情况. 如果你遇到此处未涵盖的情况, 请随时评论, 以便我对指南进行相应更新.
编码快乐! Stay GOLD!