一、前言
1.1 异步
在开发中,常常遇到需要异步完成的操作,例如网络请求。由于网络请求比较耗时,需要放在子线程去工作,而主线程可以继续与用户交互。Kotlin的协程,本质上是一个线程框架,它可以方便的切换线程的上下文。
1.2 回调
在子线程完成耗时的操作后,通常是通过回调来更新界面。回调的实现较为简单适合简单的场景,如果是比较复杂的场景,比如多次请求网络,下一次的请求依赖上一次的请求结果的话,这种结构的代码无论是阅读起来还是维护起来都是极其糟糕的。
//客户端顺序进行三次网络异步请求,并用最终结果更新UI
request1(parameter) {
value1 ->
request2(value1) {
value2 ->
request3(value2) {
value3 ->
updateUI(value3)
}
}
}
1.3 协程作用
- 协程可以让异步代码同步化。
- 协程可以降低异步程序的设计复杂度。
二、初识协程
2.1 添加依赖
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.1.1'
2.2 举个例子
fun main(args: Array<String>) {
println("Start")
GlobalScope.launch(Dispatchers.Main) {
delay(1000L)
println("Hello World")
}
println("End")
}
/*
运行结果: ("Start"会立即被打印,接下来打印"End", 1000毫秒之后, "Hello, World!"会被打印)
Start
End
Hello, World!
*/
上述代码使用launch方法启动了一个协程,launch后面的花括号就是协程,花括号内的代码就是运行在协程内的代码。
如果不用协程,就是下面的样式:
fun main(args: Array<String>) {
println("Start")
Thread {
Thread.sleep(1000L)
println("Hello World")
}.start()
println("End")
}
两段代码看起来长得几乎一模一样,运行结果也完全一致。那究竟协程的神奇之处在哪里呢?
如果我们在上面两段代码的所有输出的位置上全部加上输出当前线程名的操作:
fun main(args: Array<String>) {
//协程代码
println("Start ${
Thread.currentThread().name}")
GlobalScope.launch(Dispatchers.Main) {
delay(1000L)
println("Hello World ${
Thread.currentThread().name}")
}
println("End ${
Thread.currentThread().name}")
fun main(args: Array<String>) {
//线程代码
println("Start ${
Thread.currentThread().name}")
Thread {
Thread.sleep(1000L)
println("Hello World ${
Thread.currentThread().name}")
}.start()
println("End ${
Thread.currentThread().name}")
}
线程代码输出为:“Start main”->“End main”->“Hello World Thread-2”。这个结果也很好理解,首先在主线程里输出"Start",接着创建了一个新的线程并启动后阻塞一秒,这时主线程继续向下执行输出"End",这时启动的线程阻塞时间结束,在当前创建的线程输出"Hello World"。
协程代码输出为:“Start main”->“End main”->“Hello World main”。前两个输出很好理解与上面一致,但是等待一秒之后协程里面的输出结果却显示当前输出的线程为主线程!
这是个很神奇的事情,输出"Start"之后就立即输出了"End"说明了我们的主线程并没有被阻塞,等待的那一秒钟被阻塞的一定是其他线程。但是阻塞结束后的输出却发生在主线程中,这说明了一件事:协程中的代码自动地切换到其他线程之后又自动地切换回了主线程!这不正是我们一直想要的效果吗?
2.3 launch方法
接着来深入了解一下launch方法的声明:
public fun CoroutineScope.launch(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> Unit): Job {
...}
launch方法的参数:
context
:协程上下文,可以指定协程运行的线程。默认与指定的CoroutineScope中的coroutineContext保持一致,比如GlobalScope默认运行在一个后台工作线程内。也可以通过显示指定参数来更改协程运行的线程,Dispatchers提供了几个值可以指定:Dispatchers.Default
、Dispatchers.Main
、Di