Kotlin Jetpack 实战 02 Kotlin

简介

本文假设各位已经有了 Kotlin 基础,对 Kotlin 还不熟悉的小伙伴可以去看我之前发的文章–>《Kotlin Jetpack 实战》

本文将带领各位一步步将 Demo 工程 的 Gradle 脚本改成 Kotlin DSL,让我们一起实战吧!

正文

  1. Kotlin 编写 Gradle 脚本的优势

Kotlin Groovy
自动代码补全 支持 不支持
是否类型安全 不是
源码导航 支持 不支持
重构 自动关联 手动修改
  1. 实战前的准备

  • 将 Android Studio 版本升级到最新
  • 将我们的 Demo 工程 clone 到本地,用 Android Studio 打开:
    github.com/chaxiu/Kotl…
  • 切换到分支:chapter_02_kotlin_dsl_training
  • 强烈建议读者跟着本文一起实战,实战才是本文的精髓。
  1. 开始重构

3-1. 将单引号替换成双引号

替换前:

1
groovy复制代码apply plugin: ‘com.android.application’

替换后:

1
groovy复制代码apply plugin: "com.android.application"

小结:

  • 不用修改 Gradle 文件扩展名,直接使用 Android Studio 替换功能即可。
  • 为什么能够直接替换?因为 Grooovy 和 Kotlin 在字符串定义的语法是相近的:双引号表示字符串。
  • 那么,为什么要替换呢?因为单引号 双引号在 Groovy 里都是定义字符串,而 Kotlin 里单引号定义的是单个字符双引号才是定义字符串。

具体细节可以看我这个 GitHub Commit

3-2. 修改 Gradle 文件扩展名

    1. builde.gradle –> build.gradle.kts
    1. settings.gradle –> settings.gradle.kts
    1. Sync 走起!

Script compilation errors:
Line 1: include “:app” Unexpected tokens (use ‘;’ > to separate expressions on the same line)
Line 1: include “:app” Function invocation ‘include(…)’ > expected
2 errors

不要慌!
报错不可怕,不报错才可怕!最起码我们知道哪里错了。
错误日志告诉我们,问题出在这里:

1
2
groovy复制代码// settings.gradle
include ":app"

我们 Command + 鼠标左键点击 include,来看看源码实现:

1
2
kotlin复制代码override fun include(vararg projectPaths: String?) =
delegate.include(*projectPaths)

哟!原来 settings.gradle 里面的 include 的本质就是个方法调用啊!再结合报错原因:Function invocation 'include(...)' > expected,这就单纯是个语法错误呗!就是说,我们改了 Gradle 扩展名以后,IDE 就认为它是个 Kotlin 语句了。而 include ":app"用的还是 Groovy 的语法,这当然会报错了!

修改成这样就好了:

1
2
kotlin复制代码// 调用 include 方法,传入一个字符串":app"
include(":app")

接下来重复这个的步骤:Sync –> 报错 –> Command + 鼠标左键 看源码

修改前:

1
2
3
groovy复制代码dependencies {
classpath "com.android.tools.build:gradle:4.0.0"
}

修改后:

1
2
3
kotlin复制代码dependencies {
classpath("com.android.tools.build:gradle:4.0.0")
}

3-3. 遇到无法解决的报错怎么办?

比如:如果你继续 Sync,报错的是这里:

1
2
3
groovy复制代码task clean(type: Delete) {
delete rootProject.buildDir
}

e: /KotlinJetpackInAction/build.gradle.kts:19:16: Expecting ‘)’
e: ../KotlinJetpackInAction/build.gradle.kts:19:16: Unexpected tokens (use ‘;’ to separate expressions on the same line)
e: ../KotlinJetpackInAction/build.gradle.kts:20:23: Expecting an element
e: ../KotlinJetpackInAction/build.gradle.kts:20:32: Expecting an element
e: ../KotlinJetpackInAction/build.gradle.kts:19:1: Function invocation ‘task(…)’ expected
e: ../KotlinJetpackInAction/build.gradle.kts:19:1: None of the following functions can be called with the arguments supplied:
public abstract fun task(p0: String!): Task! defined in org.gradle.api.Project
public abstract fun task(p0: String!, p1: Closure<(raw) Any!>!): Task! defined in org.gradle.api.Project
public abstract fun task(p0: String!, p1: Action<in Task!>!): Task! defined in org.gradle.api.Project
public abstract fun task(p0: (Mutable)Map<String!, *>!, p1: String!): Task! defined in org.gradle.api.Project
public abstract fun task(p0: (Mutable)Map<String!, >!, p1: String!, p2: Closure<(raw) Any!>!): Task! defined in org.gradle.api.Project
e: ../KotlinJetpackInAction/build.gradle.kts:19:12: Function invocation ‘type(…)’ expected
e: ../KotlinJetpackInAction/build.gradle.kts:19:12: Unresolved reference. None of the following candidates is applicable because of receiver type mismatch:
public inline fun ObjectConfigurationAction.type(pluginClass: KClass<
>): ObjectConfigurationAction defined in org.gradle.kotlin.dsl
e: ../KotlinJetpackInAction/build.gradle.kts:20:5: Function invocation ‘delete(…)’ expected
e: ../KotlinJetpackInAction/build.gradle.kts:20:12: Unresolved reference: rootProject

好可怕,这回一次性报好多错误,而且看起来都很奇怪。怎么办?
不要慌!
Kotlin 官方都已经给我们准备好了迁移指南:
Migrating build logic from Groovy to Kotlin

嫌上面都迁移指南太长?还是纯英文?
不要怕!
Kotlin 官方给我们准备了迁移案例:
kotlin-dsl-samples:hello-android

看!迁移案例里已经告诉我们怎么改这个 clean task 了:

1
2
3
4
kotlin复制代码// 具体看这里:https://github.com/gradle/kotlin-dsl-samples/blob/master/samples/hello-android/build.gradle.kts
tasks.register("clean", Delete::class) {
delete(rootProject.buildDir)
}

3-4. 参照kotlin-dsl-samples继续修改

修改前:

1
groovy复制代码apply plugin: "com.android.application"

修改后:

1
2
3
kotlin复制代码plugins {
id("com.android.application")
}

修改前:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
groovy复制代码android {
compileSdkVersion 29

defaultConfig {
applicationId "com.boycoder.kotlinjetpackinaction"
minSdkVersion 21
targetSdkVersion 29
versionCode 1
versionName "1.0"

testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}

buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro"
}
}
}

修改后:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
kotlin复制代码android {
compileSdkVersion(29)

defaultConfig {
applicationId = "com.boycoder.kotlinjetpackinaction"
minSdkVersion(21)
targetSdkVersion(29)
versionCode = 1
versionName = "1.0"

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

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

修改前:

1
2
3
4
5
6
7
8
groovy复制代码dependencies {
//省略部分...
implementation fileTree(dir: "libs", include: ["*.jar"])
implementation "androidx.appcompat:appcompat:1.1.0"
testImplementation "junit:junit:4.12"
androidTestImplementation "androidx.test.ext:junit:1.1.1"
annotationProcessor "com.github.bumptech.glide:compiler:4.8.0"
}

修改后:

1
2
3
4
5
6
7
8
kotlin复制代码dependencies {
//省略部分...
implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.jar"))))
implementation("androidx.appcompat:appcompat:1.1.0")
testImplementation("junit:junit:4.12")
androidTestImplementation("androidx.test.ext:junit:1.1.1")
annotationProcessor("com.github.bumptech.glide:compiler:4.8.0")
}

具体可以看我这个 Github Commit

3-5 大功告成!

这下我们可以开始愉快的用 Kotlin 写 Gradle 脚本了。
那么,这篇文章是不是就该结束了呢?并没有。
本文是以实战为核心,咱们刚用 Kotlin DSL 重构完项目,当然还要再实战一波啦!

  1. Kotlin DSL 实战–依赖管理

4-1. Groovy 时代的依赖管理

以前我们这么定义依赖:

1
2
3
4
5
6
7
8
9
10
11
groovy复制代码// 根目录下的 builde.gradle
ext {
versions = [
support_lib: "28.0.0",
glide: "4.8.0"
]
libs = [
support_annotations: "com.android.support:support-annotations:${versions.support_lib}",
glide: "com.github.bumptech.glide:glide:${versions.glide}"
]
}

然后这么用:

1
2
3
groovy复制代码// app 目录下的 builde.gradle
implementation libs.support_annotations
implementation libs.glide

4-2. Kotlin 时代的依赖管理

Kotlin DSL 管理依赖的方式很多,我这里采用相对主流的做法:buildSrc目录下管理。具体细节可以看这个官方文档:Gradle Documentation

简单翻译:

Gradle 运行的时候,会去检查工程根目录下是否存在buildSrc目录,如果存在,这个目录下的所有脚本都会自动被添加到工程的环境变量(classpath)里。

借助上面的机制,我们就可以把所有依赖的都以常量形式定义到 buildSrc目录下,然后我们就可以直接在工程里随意使用了。

具体结构如下所示:

ProjectProperties.kt 定义了工程相关的属性:

1
2
3
4
5
6
7
8
9
10
11
12
kotlin复制代码// ProjectProperties.kt 定义了工程相关的属性
object ProjectProperties {
const val compileSdk = 29
const val minSdk = 21
const val targetSdk = 29

const val applicationId = "com.boycoder.kotlinjetpackinaction"
const val versionCode = 1
const val versionName = "1.0.0"

const val agpVersion = "4.0.0"
}

Libs.kt 定义了所有的依赖:

1
2
3
4
kotlin复制代码object Libs {
const val appCompat = "androidx.appcompat:appcompat:${Versions.appCompat}"
const val constraintlayout = "androidx.constraintlayout:constraintlayout:${Versions.constraintlayout}"
}

Versions.kt 定义了所有依赖库的版本号:

1
2
3
4
kotlin复制代码object Versions {
const val appCompat = "1.1.0"
const val constraintlayout = "1.1.3"
}

对应 build.gradle.kts 的修改如下:

1
2
3
4
5
kotlin复制代码dependencies {
implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.jar"))))
implementation(Libs.appCompat)
implementation(Libs.constraintlayout)
}

具体细节可以看这个 Github Commit

看,现在我们 Gradle 代码就能有自动补全的提示了。

真香!

结尾

注意:在新的工程里用 Kotlin DSL 完全替代 Groovy 是一件很简单的事情,但如果是一个年代久远的工程那就没那么容易了,大坑小坑会不少,Kotlin DSL 迁移的坑后面会讲。

下一节,我会一步步把 Demo 工程里的 Java 代码重构成 Kotlin。

都看到这了,点个赞呗!

下一章–>《Kotlin 编程的三重境界》

目录–>《Kotlin Jetpack 实战》

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

0%