Kotlin-Anko 与 安卓开发

Kotlin 是什么

首先介绍一下本文的主角:Kotlin。
——“能用于 JVM、Android、浏览器上的静态编译型语言,与 Java™ 有100%的互操作性。”,这是官网对它的描述。

我看来,Kotlin 是对 Java 的一种弥补和扩充。

弥补了 Java 的缺陷(例如空值安全),扩充了 Java 的特性(例如函数式、全局类型推倒),更是增加了众多好用的语法糖简化了编码流程。加之与 Java 互操作容易。将其用于 JAVA 平台可以说实用性很强。

Kotlin 能给安卓带来什么

Kotlin 对闭包、高阶函数的支持能让 Android 上基于事件的回调开发代码更加自然。加上大幅度精简编码流程,更优雅的实现功能。还有很重要的一点就是本文重点之二,Anko 框架,它给安卓开发带来了界面编程新的风格,而这种风格离不开一个叫做高阶函数的东西,那么?

什么是高阶函数

高阶函数是函数式编程特性所带来的一种函数风格,将“函数”作为“参数”的函数、或将“函数”作为“值”返回的函数,便是高阶函数。
例如定义一个这样的 Kotlin 函数:

fun doIt(f: () -> Unit): Unit {
              f()
          }
          

参数: f 即是一个函数类型的参数,这便是一个“高阶函数”。我们调用它:

doIt({
              println("嘻嘻")
          })
          

输出:

嘻嘻
          

我们可以让高阶函数传递参数出去:

//修改 doIt 函数
          fun doIt(f: (msg: String) -> Unit): Unit {
              f(Date().toString())
          }
          
          fun main(args: Array<String>) {
              // 调用doIt 输出传递出来的msg参数
              doIt({ msg ->
                  println(msg)
              })
          }
          

输出当前时间的字符串(注意这个时间字符串是在 doIt 函数中产生,在调用的代码块中使用的):

Wed Nov 23 02:36:32 CST 2016
          

关于传递的莫名奇妙代码块

{ param ->
              // some code..
          }
          

或者

{
              // some code..
          }
          

是 Kotlin 闭包的写法,上面的闭包带有 ->,左边便是参数。如果不理解闭包,可以把这样代码块当做一个匿名函数。所以传递闭包,就相当于传递了一个函数。

doIt 并没有返回参数,但是通过“函数参数”的调用将数据回传给了闭包。这就是高阶函数的意义之一。

高阶函数还未结束,Kotlin 更妙的一点是给高阶函数扩展了一个语法糖支持。那就是,当一个高阶函数的最后一个参数是函数(或者只有一个函数参数),那么传递闭包的语法可以外置。
即,我们可以这样调用 doIt:

doIt {
              //do someing...
          }
          

什么你问我这样做有什么意义?你信不信这个问题可以把我气死。因为啊这样做意义相当大,那就是 DSL 形式的编程风格。

DSL 是什么

domain-specific language(DSL):领域特定语言,先别管那有点蹩脚的翻译,先来看看那些是 DSL 。
例如 HTML、Emacs Lisp。Gradle 的配置文件 .gradle、Gem 的配置文件 Gemfile 等。

它们有一些共同点,例如都针对特定的软件(浏览器、Emacs、Gradle、Gem),逻辑性并不强,具有描述性。通过简单的编写让特定软件执行出复杂的功能。某些 DSL 也可以称为迷你语言。常用来当做配置文件或某类软件功能的扩展。当然它们通常算不上是编程语言。

这是 HTML:

<body>
              <div id='app'>我是内容</div>
          </body>
          

Emacs Lisp:

(setq my-name "Bastien")
          (insert "Hello!")
          

build.gradle:

buildscript {
              repositories {
                  jcenter()
              }
              dependencies {
                  classpath 'com.android.tools.build:gradle:2.2.2'
          
                  // NOTE: Do not place your application dependencies here; they belong
                  // in the individual module build.gradle files
              }
          }
          
          allprojects {
              repositories {
                  jcenter()
              }
          }
          
          task clean(type: Delete) {
              delete rootProject.buildDir
          }
          

Gemfile:

source 'https://gems.ruby-china.org/'
          
          gem 'sinatra', '~> 1.4', '>= 1.4.7'
          gem 'haml', '~> 4.0', '>= 4.0.7'
          gem 'rest-client', '~> 2.0'
          gem 'redcarpet', '~> 3.3', '>= 3.3.4'
          gem 'sequel', '~> 4.40'
          gem 'mysql2', '~> 0.4.5'
          gem 'test-unit', '~> 3.2', '>= 3.2.1'
          

再看括号外置的 kotlin 高阶函数:

doIt {
              //do someing...
          }
          

是不是也是同一种风格?并不像传统的编程语言代码?和上述的 gradle 配置的语法更是一样。因为 Ruby、Groovy 都支持 DSL 代码风格,配置文件其实就是程序代码。

所以这个闭包外置的语法扩展相当重要,因为它一下子将 kotlin 的 DSL 风格支持上升了好几个高度。而这种风格,非常适合用来描述界面,Anko 框架所推荐的就是放弃 XML 从而基于 DSL 风格编写安卓界面。由于 DSL 的描述性,使程序的界面代码看起来就像写配置文件一样,但是又比任何配置文件都强大得多(后面有解释原因)。

先让大家看看 DSL 风格的编码是什么样子的?

Net::SSH.start 'host', 'user', :password => "password" { |ssh|
            # capture all stderr and stdout output from a remote process
            output = ssh.exec! "hostname"
            puts output
          
            puts stdout
          
            # run multiple processes in parallel to completion
            ssh.exec "sed ..."
            ssh.exec "awk ..."
            ssh.exec "rm -rf ..."
            ssh.loop
          
            # forward connections on local port 1234 to port 80 of www.capify.org
            ssh.forward.local 1234, "www.capify.org", 80
            ssh.loop { true }
          }
          

上面不用我说也能猜出来,这是一段 ssh 到指定机器进行操作的代码。但是并不像正常的编程代码,倒像写了一段配置供某个软件使用一般。然而这其实真的是程序语言的代码,它就是属于 DSL 风格第一梯队的 Ruby 语言的一个框架 ssh-net 的示例代码,只是为了更加 DSL 经过了我的小幅度修改(当然这个修改并不被 Ruby 所推荐,原因在这里不重要)。

DSL 风格的 Anko API

verticalLayout {
              button("Say Hello") {
                  onClick { 
                    toast("Hello, Anko!")
                  }
              }
          }
          

上面的代码是什么意思?首先,其中的 verticalLayout、button、onClick 都是高阶函数。外括号即调用它们时传递的闭包,闭包中再嵌套高阶函数,就跟 XML 定义子标签一样。
调用 verticalLayout 函数会创建一个 vertical 的布局,button 在 verticalLayout 的括号中,所以在创建布局后装进一个 botton,在 botton 创建后又会添加一个 onClick 事件,onClick 的任务就是闭包中的代码,即弹出一个“Hello,Anko!”。
这就是 Anko 框架所推荐风格的安卓编程。Anko 的 API 基本都是这种形式,看官方是怎么解释替代 XML 的好处的?或者说 Anko 是为了解决 XML 那些问题而诞生的?

XML:

  • 不是类型安全的
  • 不是空值安全的
  • 它迫使你为每个布局编写几乎相同的代码
  • 设备解析 XML 会消耗时间和电池
  • 最重要的,它不能重用

非常好,这些理由真的非常好。例如,你也可以用 Kotlin 但不用 Anko,也不用 XML。它是这样子的:

val act = this
          val layout = LinearLayout(act)
          layout.orientation = LinearLayout.VERTICAL
          val name = EditText(act)
          val button = Button(act)
          button.text = "Say Hello"
          button.setOnClickListener {
              Toast.makeText(act, "Hello, ${name.text}!", Toast.LENGTH_SHORT).show()
          }
          layout.addView(name)
          layout.addView(button)
          

代码丑陋、多余、别捏和不自然,如果换做 Java 更加严重。而 Anko:

verticalLayout {
              val name = editText()
              button("Say Hello") {
                  onClick { toast("Hello, ${name.text}!") }
              }
          }
          

我不是故意推崇它,但是不得不说,如果你习惯了它也许会爱上它。那么,让我们正式的集成它进开发环境吧!

Anko 和 AndroidStudio

首先要在 AS 上装 Kotlin 插件。其实还有一个 Anko DSL Preview 插件,用来预览 Anko DSL 写的界面,但是后来不兼容新版 AS 了,所以这里不提了。
安装完 Kotlin 插件以后,AS 的 menu 栏的 Tools 会多出一个 Kotlin 选项。创建好项目以后,点击 Configuration in Project 即可把 Kotlin 集成进项目依赖和 gradle 配置。

gradle 里边会多出一些配置,然后再手动加入 Anko 依赖:

apply plugin: 'kotlin-android'
          // ...
          android {
              // ...
              sourceSets {
                  main.java.srcDirs += 'src/main/kotlin'
              }
          }
          dependencies {
              // 加上Anko依赖
              // sdk 版本根据自己创建项目的版本而做更改,目前最高支持23
              compile 'org.jetbrains.anko:anko-sdk23:0.9'
              compile 'org.jetbrains.anko:anko-support-v4:0.9'
              compile 'org.jetbrains.anko:anko-appcompat-v7:0.9'
          }
          

然后就可以用写 Kotlin 代码和 Anko 框架了,集成完毕。

究竟选择不选择 Anko

不管你接受不接受 Anko 编写界面的思想,即使你还是习惯 XML 那一套也好。毕竟像 JavaFx、WPF、甚至 Web 前端都是以扩展的 XML 来写界面,因为是天生的逻辑和界面分离。那么 Anko 把界面写在程序代码里反而倒像是倒退?当然也不是,因为可以自己组织程序代码,分离 DSL 界面和逻辑代码。

DSL 构建界面的方式貌似也挺被 Kotlin 看好和推广的,例如 Kotlin 有一个 Web 框架 Kara,它用 DSL 构建 HTML 页面代码(可以不用模板引擎)。

class DefaultLayout() : HtmlLayout() {
              override fun render(context: ActionContext, mainView: HtmlView) {
                  head {
                      title("Kara Demo Title")
                      stylesheet(DefaultStyles())
                  }
                  body {
                      h1("Kara Demo Site")
                      div(id="main") {
                          renderView(context, mainView)
                      }
                      a(text="Kara is developed by Tiny Mission", href="http://tinymission.com")
                  }
              }
          }
          

至少我不讨厌这样构建界面的方式,相比 XML/HTML 也并没有什么缺点。

总结

不管你用怎么样的方式构建界面,但是 Kotlin 绝对值得你用于安卓开发。