Android Kotlin DSL 笔记

11 阅读4分钟

一、什么是 Kotlin DSL?

Kotlin DSL(Domain Specific Language)是指使用Kotlin语言编写的Gradle构建脚本。传统的Gradle脚本使用Groovy DSL,而Kotlin DSL则是官方推出的替代方案,它允许我们以.kts为扩展名编写构建文件(如settings.gradle.ktsbuild.gradle.kts)。

为什么需要 Kotlin DSL?

  • 类型安全:Groovy是动态语言,脚本中的错误只能在运行时发现。而Kotlin是静态类型语言,IDE可以在编写时提供智能提示、自动补全和错误检查。
  • 更好的IDE支持:在IntelliJ IDEA或Android Studio中,Kotlin脚本可以像普通Kotlin代码一样获得代码导航、重构、文档查看等支持。
  • 可维护性:Kotlin代码更简洁、易读,且可以利用现有的Kotlin库和工具。
  • 平滑学习曲线:对于已经熟悉Kotlin的Android开发者,无需额外学习Groovy语法。

二、Kotlin DSL vs Groovy DSL 对比

特性Groovy DSLKotlin DSL
语言类型动态类型静态类型
脚本扩展名.gradle.gradle.kts
IDE支持有限(字符串形式配置)强(类型安全、自动补全)
性能配置阶段稍快(但类型不安全)配置阶段可能稍慢(但增量编译优化)
语法简洁性灵活但易错(如括号可省略)严格,但更清晰
迁移成本低(现有项目多数是Groovy)中(需重写脚本)

关键差异示例:

Groovy:

android {
    compileSdk 34
    defaultConfig {
        applicationId "com.example.app"
        minSdk 21
        targetSdk 34
        versionCode 1
        versionName "1.0"
    }
}

Kotlin:

android {
    compileSdk = 34
    defaultConfig {
        applicationId = "com.example.app"
        minSdk = 21
        targetSdk = 34
        versionCode = 1
        versionName = "1.0"
    }
}

注意Kotlin DSL中需要显式使用赋值符号=,而Groovy中常常省略。


三、Kotlin DSL 基本语法

1. 项目配置文件 settings.gradle.kts

pluginManagement {
    repositories {
        google()
        mavenCentral()
        gradlePluginPortal()
    }
}
dependencyResolutionManagement {
    repositories {
        google()
        mavenCentral()
    }
}
rootProject.name = "MyApplication"
include(":app", ":common", ":feature_home")

2. 模块级 build.gradle.kts

plugins {
    id("com.android.application")
    id("org.jetbrains.kotlin.android")
}

android {
    namespace = "com.example.myapp"
    compileSdk = 34

    defaultConfig {
        applicationId = "com.example.myapp"
        minSdk = 21
        targetSdk = 34
        versionCode = 1
        versionName = "1.0"

        testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
    }

    buildTypes {
        release {
            isMinifyEnabled = true
            proguardFiles(
                getDefaultProguardFile("proguard-android-optimize.txt"),
                "proguard-rules.pro"
            )
        }
        debug {
            isMinifyEnabled = false
        }
    }

    compileOptions {
        sourceCompatibility = JavaVersion.VERSION_1_8
        targetCompatibility = JavaVersion.VERSION_1_8
    }

    kotlinOptions {
        jvmTarget = "1.8"
    }
}

dependencies {
    implementation("androidx.core:core-ktx:1.9.0")
    implementation("androidx.appcompat:appcompat:1.6.1")
    testImplementation("junit:junit:4.13.2")
    androidTestImplementation("androidx.test.ext:junit:1.1.5")
}

3. 依赖管理

Kotlin DSL中依赖项是强类型的,可以使用字符串或类型安全的方式(如果使用Version Catalog)。

传统方式

dependencies {
    implementation("com.squareup.retrofit2:retrofit:2.9.0")
}

使用Version Catalog(推荐): 首先在gradle/libs.versions.toml中定义:

[versions]
retrofit = "2.9.0"

[libraries]
retrofit = { module = "com.squareup.retrofit2:retrofit", version.ref = "retrofit" }

然后在脚本中:

dependencies {
    implementation(libs.retrofit)
}

这种方式不仅类型安全,而且支持自动补全。


四、实际使用案例

案例1:统一版本管理(buildSrc 方式)

在项目根目录创建buildSrc模块(纯Kotlin项目),用于集中管理版本和依赖。

buildSrc/build.gradle.kts:

plugins {
    `kotlin-dsl`
}

repositories {
    google()
    mavenCentral()
}

buildSrc/src/main/java/Dependencies.kt:

object Versions {
    const val compileSdk = 34
    const val minSdk = 21
    const val targetSdk = 34
    const val kotlin = "1.9.0"
    const val appcompat = "1.6.1"
}

object Libs {
    const val kotlinStdlib = "org.jetbrains.kotlin:kotlin-stdlib:${Versions.kotlin}"
    const val appcompat = "androidx.appcompat:appcompat:${Versions.appcompat}"
}

然后在模块的build.gradle.kts中:

android {
    compileSdk = Versions.compileSdk
    // ...
}

dependencies {
    implementation(Libs.kotlinStdlib)
    implementation(Libs.appcompat)
}

这种方式完全类型安全,且所有版本集中管理。

案例2:自定义构建配置(BuildConfig字段)

动态添加BuildConfig字段:

android {
    defaultConfig {
        buildConfigField("String", "GIT_COMMIT", "\"${getGitCommit()}\"")
    }
}

fun getGitCommit(): String {
    return try {
        val process = ProcessBuilder("git", "rev-parse", "--short", "HEAD").start()
        process.inputStream.bufferedReader().readText().trim()
    } catch (e: Exception) {
        "unknown"
    }
}

案例3:自定义Task

创建一个Task用于生成版本文件:

tasks.register("generateVersionFile") {
    doLast {
        val versionFile = File(buildDir, "generated/version/version.txt")
        versionFile.parentFile.mkdirs()
        versionFile.writeText("""
            Version: ${android.defaultConfig.versionName}
            Code: ${android.defaultConfig.versionCode}
        """.trimIndent())
    }
}

// 让preBuild依赖它
tasks.named("preBuild") {
    dependsOn("generateVersionFile")
}

案例4:多维度变种(productFlavors)

配置多渠道:

android {
    flavorDimensions += "version"
    productFlavors {
        create("free") {
            dimension = "version"
            applicationIdSuffix = ".free"
            versionNameSuffix = "-free"
        }
        create("pro") {
            dimension = "version"
            applicationIdSuffix = ".pro"
            versionNameSuffix = "-pro"
        }
    }
}

案例5:使用Gradle Version Catalog统一依赖

gradle/libs.versions.toml中:

[versions]
kotlin = "1.9.0"
androidGradlePlugin = "8.1.0"

[libraries]
android-gradle-plugin = { module = "com.android.tools.build:gradle", version.ref = "androidGradlePlugin" }
kotlin-gradle-plugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" }

[plugins]
android-application = { id = "com.android.application", version.ref = "androidGradlePlugin" }
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }

在根build.gradle.kts中应用插件:

plugins {
    alias(libs.plugins.android.application) apply false
    alias(libs.plugins.kotlin.android) apply false
}

在模块中直接使用别名应用插件:

plugins {
    alias(libs.plugins.android.application)
    alias(libs.plugins.kotlin.android)
}

五、常见陷阱与最佳实践

1. 类型转换与作用域

Kotlin DSL中,很多Gradle DSL方法返回Provider类型,需要调用.get()或使用委托。例如:

android.defaultConfig.applicationId.get() // 获取值

但通常直接赋值即可,Gradle插件会自动处理。

2. 配置文件缓存

Kotlin DSL支持Gradle的配置缓存,但需要确保自定义Task正确标注输入输出,否则可能破坏缓存。例如:

abstract class MyTask : DefaultTask() {
    @get:Input
    abstract val input: Property<String>

    @TaskAction
    fun action() {
        println(input.get())
    }
}

3. 避免使用Groovy风格的动态特性

不要在Kotlin DSL中使用Groovy的[]语法操作集合,应使用Kotlin标准库函数。例如:

// 错误
android.sourceSets.main.manifest.srcFile 'src/main/AndroidManifest.xml'

// 正确
android.sourceSets.getByName("main").manifest.srcFile("src/main/AndroidManifest.xml")

4. 迁移策略

现有项目从Groovy迁移到Kotlin DSL可以逐步进行:

  • 先将settings.gradle重命名为settings.gradle.kts,并修正语法。
  • 然后将根build.gradle迁移。
  • 最后逐个模块迁移,每次迁移一个模块并测试构建是否成功。

迁移时可以利用Android Studio的自动转换功能(虽然不完全可靠,但能提供基础转换)。

5. 性能考虑

Kotlin DSL在配置阶段可能比Groovy稍慢,但Gradle 7.0+通过增量编译和配置缓存大大缩小了差距。可以启用以下优化:

# gradle.properties
org.gradle.configuration-cache=true
org.gradle.parallel=true

六、总结

Kotlin DSL是Gradle构建脚本的未来,它带来了类型安全、IDE友好和更好的可维护性。虽然初期迁移可能有些麻烦,但长期收益巨大——尤其是对于大型多模块项目。结合Version Catalog和buildSrc,我们可以构建出清晰、健壮的构建系统。

作为资深开发者,我强烈建议新项目直接采用Kotlin DSL,现有项目可以逐步迁移。如果你还在犹豫,不妨先从新建模块开始尝试,体验一下代码补全和即时错误提示带来的畅快感。