版权声明
本文作者:Doug Stevenson
译者:程大治
本文由作者授权翻译并发布,首发于移动开发前线公众号,未经译者授权禁止转载。
在过去的一年中,在 Android 开发圈有一个越来越火的话题,就是 JetBrains 开发的新 JVM 语言 Kotlin。这个团队还开发了 IntelliJ Idea,也就是 Android Studio 的基础。Kotlin 旨在通过全新的语言特色来替代老旧而不 cool 的 Java,又由于 Kotlin 可以 100% 兼容 Java,所以你在项目中可以想用多少用多少。而又因为 Kotlin 的标准库很小,很适合在资源有限的移动设备上开发使用。
Kotlin 能干所有 Java 能干的(不止),且语法更准确,代码更好看,而且在 IntelliJ 和 Android Studio 中有很好的支持。我从 2009 年开始从事深层次 Android 开发工作,我尤其关心 Kotlin 可以给 Android 开发者带来什么。所以我就不说虚的了,直接开始写 Kotlin 代码,让大家感受其语言特点,希望最终能带给大家有用的信息。
在第一部分中,我会通过最简单的方式在一个新 Android 项目中集成 Kotlin。
在 Android 项目中配置 Kotlin
官方文档讲了如何一步步安装 Kotlin 插件,并使用插件在 Android 项目中自动修改 Gradle 文件来添加对 Kotlin 的支持。我不建议大家这么做,因为这样自动完成的结果可能并不完美,即使修改后的 Gradle 文件工作正常,也会打乱 Android 项目中 Gradle 文件的一般形式。
说实话,我一直不喜欢有些 Android Studio 插件直接修改 Android build 文件,因为经常搞得很乱,我又得一点一点清理直到符合我的风格。Gradle 构建文件也是源代码,而这些插件并不擅长修改已经存在的代码。所以如果你也和我一样有些挑剔,那就多花一分钟和我一起手动配置。
下面我们要分四步完成 Kotlin 的配置。
- 新建一个 Android 项目。
- 修改 Gradle 代码来添加 Kotlin Gradle 插件与标准库。
- 在 IntelliJ 或 Android Studio 中添加 Kotlin 插件。
- 将 Java 类文件转换成 Kotlin。
首先,直接以默认方式新建一个 Android 项目,此时应该自带一个 Activity。之后,要在两个 build.gradle 文件中添加五行重要代码,我都在其后添加了注释。下面让我们先修改最高层的 build.gradle 脚本,添加两行代码。
这样就会在项目构建时添加 Kotlin Gradle 插件。请注意上面在 ext.kotlin_version 中标注的 kotlin 版本字符串,我们一会还要在 app 模块的 compile dependencies 中用到它,而且两个地方版本必须相符。你最好使用官方文档中最新版本。
然后,在app 模块自己的build.gradle 文件中紧随Android plugin 添加kotlin-android plugin。这样整个项目就整合了Kotlin,在build 项目时会编译Kotlin 文件,这样最后所有的类文件都会打包在一个app 中。
惯例上,Kotlin 文件存放在src/main/kotlin 路径中,但也可以把他们和Java 文件一起放在/src/main/java 路径中。这里我们还是按照惯例,并在Gradle 中标注一个新的Kotlin 源路径。
不要忘了新建这个路径,一会就要用到了。最后需要添加一个Kotlin 依赖,直接使用build.gradle 中的kotlin 版本变量。
不过这个包有多大呢?好问题!每当我们添加新的依赖时,都应该搞清楚这个包有多大。不过对于这个问题,我会在后面的文章中回答。
这就是Kotlin Gradle 插件,走完这些步骤后,就可以在项目中运行Kotlin 代码了。不过你还需要添加IDE 对Kotlin 的支持,所以如果你还没有安装IntelliJ 或Android Studio 的Kotlin 插件,那就赶快安装。安装Kotlin 插件就像安装其他任何插件一样,可以在Preferences->Plugins->Insall JetBrains plugin 下找到。安装后要重启IDE,做完这一步后,准备工作就完成了。我发现IDE 对Kotlin 的支持甚至和Java 语言一样好。这也可以理解,毕竟IDE 和Kotlin 都是JetBrains 开发的嘛。
快速将Java 转成Kotlin
IDE 插件有一个很有趣的功能就是将 Java 文件直接转成 Kotlin。这个插件可以很智能地将 Java 语言风格转换成 Kotin 风格并保持运行兼容。如果你创建了一个 Android 项目,那就找到自动生成的 MainActivity,在左边的项目结构中选中,并触发 IDE 的 action “Convert Java File to Kotlin File”。你可以按下快捷键 Command+Shift+A(OSX) 来选择 action。这个插件甚至有专门针对这个 action 的快捷键 Option+Shift+Command+K(OSX)。其实官方并不建议直接转换 Java 文件,但直到现在我还没遇到过什么问题。
如果你按我说的操作转换了 Java 文件,就会在原本.java 文件的地方找到一个.kt 文件。
你可以看到现在 MainActivity 在右下角有一个 K 标志(这里隐藏了.kt 扩展名)。由于我们刚刚专门为 Kotlin 配置了一个路径,我们把 Kotlin 文件拖进 kotlin 文件夹中。注意要保留 kotlin 文件中类的包名,不然项目无法运行。
如果你想在项目中只用 Kotlin,那可以干脆删除掉 Java 文件夹,把所有的 Kotlin 文件放在 kotlin 路径中。之后,项目结构就像下面这样。
从新的 Activity 中你可以大概知道 Kotlin 长什么样。我下面说几点 Kotlin 与 Java 很不一样的地方:
- 在 Kotlin 中你见不到"new"关键字。
- 把类名当做方法并传入参数就可以直接构造对象。
- 数据类型关键字被 val(final) 与 var(variable) 取代,Kotlin 可以自己判断数据是什么类型。
中场休息
刚才我们一步一步在 Android 项目中添加了 Kotlin 支持,现在我们要开始通过代码直观感受 Kotlin 的语言特色以及它能如何简化 Android 开发。
当我第一次接触 Kotlin 并了解其语言特色功能时,有一点让我感触很深,那就是 type-safe builders。它让你以陈述式语言风格来创建对象,其语法很类似 Gradle,但 Gradle 和 Groovy 代码是动态编写的,而 kotlin 是静态编写的,所以编译器可以在属性的值不合法时告诉你。
type-safe builders 的一个典型用法就是构建嵌套式数据结构,比如 XML。在 Android 中有很多 XML 文件,如 layouts 和 views。如果 Kotlin 可以以程序的方式动态编写 XML,它可能很善于处理层级问题。所以我决定尝试使用 Kotlin 创建一个动态构建 View 层级的工具。如果是在 Java 中做这件事,代码量之大可以想象。
注意:在后面的代码中我会经常使用 lambda,所以在继续阅读之前,确保你了解 lambda 的基本形式。简而言之,lambda 是要作为参数被传入某方法或赋值给某变量的匿名方法的简化表现形式。
type-safe builders 可行还要归功于 Kotlin 的一个特色功能:lambda with receiver。下面我们要看一个能真正有用的案例。kotlin 可以在类的外面定义方法,我在这里就是这么干的。还要注意变量的名字是在类型之前的,这一点与 Java 语言正相反。
(点击放大图像)
简便起见,我将上面的方法命名为v,在之后的系列文章中我还会使用到这个方法。这个方法是这么调用的:
这段代码和填充下面这段XML 是等效的。
OK,如果这是你第一次看 Kotlin 代码,相信有不少需要翻译的。下面解释一下刚才代码中涉及的几个语法。
reify 的意思是具体化。而作为 Kotlin 的一个方法泛型关键字,它代表你可以在方法体内访问泛型指定的 JVM 类对象。这段代码的意思就是 v 方法要使用命名为 TV(意为 Type of View,即 View 种类) 的 reified 泛型,它指定类必须为 View 或其子类。必须以内联方式声明这个方法才有效。调用者要给 TV 指定一个具体的类型。
init: TV.() -> Unit
v 方法有两个参数,一个是 Context,一个是 lambda 风格的 init。init 在这里很特殊,因为它是一种 lambda with receiver 类型的方法引用。lambda with receiver 是一个要求特定类型的对象的代码块,这里要求的对象在 lambda 代码中通过 this 关键字引用。在这里 receiver 对象就是 reified 泛型 TV。
在我们这个例子中,v 要创建一个类型为 TV 的对象,需要调用者告诉它如何创建。这个新创建的 TV 类型对象会成为给定的 lambda 的 receiver,labmda 通过 view.init() 在 v 中被调用,以便对 view 进行操作。“-> Unit”意思是 lambda 返回 Unit 类型,就如 Java 中的 Void 一样,即什么也不返回。
总结一下这里的 lambda with receiver:
- v 声明了一个名叫 init 的参数,它是 TV 类型的 lambda with receiver。
- v 创建并初始化一个 TV 对象,并在其上调用 lambda 来初始化它。
- lambda 在自己代码块中通过 this 关键字引用 TV 对象。
TV::class.java
通过 TV::class.java 表达你可以引用 reified 泛型 TV 的 Class 对象。这种表达在 Kotlin 中专门针对 reified 泛型,与 Java 相比,可以大量减少代码。
到这里,估计你会有一些问题:
为什么 v 有两个参数,而调用的时候似乎只给了一个?
这个只是因为 Java 程序员对 Kotlin 语法并不熟悉。在 Java 中,一个方法的所有的参数都要写在圆括号内,所以当有匿名回调方法时会变得很长。但在 Kotlin 中,当 lambda 时最后一个参数时,有一个特殊语法,即 lambda 出现在紧随圆括号的大括号中。你可以把所有的代码都放在圆括号内,但大多数情况下这么写代码会显得更加简洁,而且可以使一组圆括号保持在一行里,更易于追踪。另外,这种语法在传入只有一个方法的匿名内部类比如 Runnable 时也可以派上用场。
layoutParams 和 text 是变量吗?
lambda with receiver 的一个语法特色就是当操作 this 的方法或属性时 this 关键字可以省略。但在上面的调用例子中 layoutParams 和 text 到底是什么?其实这些属性是 Kotlin 提供的 receiver 类型(TV)的属性。因为 TextView 有 setLayoutParams() 与 setText() 等方法,Kotlin 会自动识别这些 JavaBean 风格的方法并为他们创建属性,好像它们是类的成员变量一样。所以这里 text = "Hello"等价于 this.setText(“Hello”)。下面是在安装了 Kotlin 插件的 Android Studio 中的截图,里面展示了自动完成的内容。
如你所见,Kotlin 插件之处 text 属性是从 TextView(receiver 对象)中 JavaBean 风格的 getter/setter 方法中派生出的。
构造器到底是怎么运作的?就不能 new TV(context) 吗?
因为编译器也不知道在 v 方法内部 TV 到底是什么类型,我们不能 new 它的对象。不过我们可以利用 reified Class 对象(TV::class.java)来获取仅有一个 context 参数的构造器。View 都有这样一个构造器,不然我们也写不出这个程序。我们通过构造器对象来获得 TV 类型的实例,就如 Java 中的 new 关键字一样。想一想我们可以通过一个方法适配所有 View,而不再针对每个 View 单独写方法,这一点小麻烦还是很值的。在后续系列文章中我们也会优化这个方法。
这几行代码真的很大的简化了我们的工作。如果你刚刚接触 Kotlin,建议你从头再消化一遍,毕竟许多概念和 Java 差别很大。我自己也花了好多时间来理解这些概念。
这些都只是开始,这个方法有很多地方可以完善和升级,使之更加易用。比如我想用用一行代码构造一整个 View 层级。所以不要走开,我会在后续文章探索 Kotlin 还能做什么。
感谢徐川对本文的审校。
给 InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家通过新浪微博( @InfoQ , @丁晓昀),微信(微信号: InfoQChina )关注我们。
评论