Kotlin Android 开发当中使用 coroutines 实现异步操作
背景
本文写作于 2017年10月8日,kotlin
语言版本为 1.1,kotlinx.coroutines
版本为 0.19.1,其中协程为实验特性。需要指定编译器选项以启用此功能。实验阶段表明此功能稳定性不能得到保证,请慎用!
Kotlin 官网上对于协程实现的大概介绍:https://kotlinlang.org/docs/reference/coroutines.html
kotlinx.coroutines 协程库:https://github.com/kotlin/kotlinx.coroutines
概述
协程可能是近几年来最新的并行/并发模型实现,常见语言中最早的实现可能是lua
,近年来新晋语言golang
的崛起更把这个概念发扬光大。
Kotlin 的实现设计思路有以下几个特点:
- 尽可能不使用核心语言,而是标准库来实现。
- 标准库只提供核心功能,官方的第三方库提供应用层接口。
- 异步等操作使用协程封装,同时协程可用于其他方面尽可能替代线程。
尽管如此,官方也不得不加入suspend
关键字来标记协程可挂起函数,同时协程的实现也需要编译器支持。
suspend
关键字与C#
等语言中的async
关键字很接近。线程的调度由操作系统完成,而协程需要由运行时管理。为了标记一个函数可以被运行时挂起,需要此关键字。当然协程的范围比异步更广,这里不再赘述。
实现
本部分中我们通过完成kotlinx.coroutines
官方教程来实际体验一下kotlin
的协程。https://github.com/Kotlin/kotlinx.coroutines/blob/master/coroutines-guide.md 这篇文档为kotlinx
协程的 tutorial,我们主要完成 https://github.com/Kotlin/kotlinx.coroutines/blob/master/ui/coroutines-guide-ui.md 这一篇专注于 UI 编程的 tutorial。首先可以 clone https://github.com/Kotlin/kotlinx.coroutines 这个 repo,其中 ui/kotlinx-coroutines-android/example-app/ 目录下有 tutorial 用到的脚手架。
使用 Android Studio 打开项目,跟着向导一通安装 SDK 之后可以通过 build 了,来看一下 MainActivity.kt 目前的内容:
1 | package com.example.app |
在 UI 中使用 coroutine
向文件头部添加如下 import:
1 | import kotlinx.coroutines.experimental.android.UI |
将 setup 函数替换为:
1 | fun setup(hello: TextView, fab: FloatingActionButton) { |
运行效果图如下:
如何做到点击右下角圆形悬浮按钮就停止倒计时呢?在前面的 launch(UI) { … } 随后的 lambda 表达式后链式调用以下内容:
1 | .apply { |
随后刷新,调试。可以看到点击后倒计时停住不动了。
Standard.kt 中定义的 let
apply
等 one-liner 工具函数的说明可以查阅文档:https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/index.html
在 UI 上下文中可以使用 actor 完成进阶功能,比如限制线程上同时存在的协程个数。这些需求比较高级,需要使用时请查阅文档即可。
在非 UI 上下文中执行阻塞耗时的操作
这种操作主要有两种,一是计算密集型,占用大量 CPU 时间使得函数无法及时返回,造成阻塞的。本文将要举的例子是 Fibonacci 数列的计算。还有一种是 IO 密集型,比如最常见的网络访问。Android 系统是禁止 UI 线程访问网络的,这样的尝试会被强行抛异常。下面介绍一下具体做法。
普通的 fib 函数:
1 | fun fib(x: Int): Int = if (x <= 1) 1 else fib(x - 1) + fib(x - 2) |
特效版本的 fib 函数:
1 | import kotlinx.coroutines.experimental.run |
可见我们只要显式指定协程池为 CommonPool 即可。这里的 run 函数比起 launch 来,区别在于可以返回值而非 Unit。
async/await
这部分内容没有包含在原文当中,但是对于了解过其他语言中这种编程范式的读者来说很有必要也很易懂。
大概用法就是:
1 | val job = async { ... } |
运行环境等等问题和前文一样啦。仍然可以指定运行的上下文。
总结
和本人较为熟悉的 C#/Javascript 相比起来,语法噪音稍稍多了一点点,但是带来的掌控性与灵活性,还有协程实现本身的高性能还是值得的。更多感受还要深入使用以后才有感觉。
由于作者水平有限,如有错漏,还请指正。