协程的开发人员 Roman Elizarov 是这样描述协程的:协程就像非常轻量级的线程。线程是由系统调度的,线程切换或线程阻塞的开销都比较大。而协程依赖于线程,但是协程挂起时不需要阻塞线程,几乎是无代价的,协程是由开发者控制的。所以协程也像用户态的线程,非常轻量级,一个线程中可以创建任意个协程。
添加依赖:coroutines库在Kotlin1.3版本的时候已经升级为正式版,命名为1.0.0。
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.0.0'
// 可以添加Android的依赖
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.0.0'
fun main(args: Array) {
GlobalScope.launch { // 在后台启动一个新的协程并继续
delay(1000L) // 非阻塞线程1s
println("World!") // 在延迟后打印输出
println("This is a coroutines ${TimeUtil.getTimeDetail()}")
}
println("Hello,") // 主线程的协程将会继续等待
Thread.sleep(2000L) // 阻塞线程2s,保证JVM存活,协程可正常执行完
println("main end ${TimeUtil.getTimeDetail()}")
}
// 输出
// Hello,
// World!
// This is a coroutines 10:49:58
// main end 10:49:59
在线程环境中可直接使用CoroutineScope.launch启动一个新的协程,它的参数有如下三个,分别为:
返回值为Job对象。Job有如下几个重要的方法,分别为:
job.start()可配合LAZY启动一个协程
fun main(args: Array){
val job = GlobalScope.launch(start = CoroutineStart.LAZY) {
println("this is a job")
}
job.start()
Thread.sleep(1000L)
}
// 输出
// this is a job
job.join()等待协程执行完毕
suspend fun main(args: Array){
val job = GlobalScope.launch {
println("this is a job")
}
job.join()
}
// 输出
// this is a job
注意:join()函数是一个挂起函数,所有main必须被suspend修饰。
job.cancel()取消一个协程
suspend fun main(args: Array){
val job = GlobalScope.launch {
println("this is a job")
}
job.cancel()
job.join()
}
// 无输出,协程被取消了
job.cancelAndJoin()等待协程执行完毕然后再取消
suspend fun main(args: Array){
val job = GlobalScope.launch {
println("this is a job")
}
job.cancelAndJoin()
}
// 输出
// this is a job
job.cancelAndJoin()也是一个挂起函数。
从上面的代码和输出可以看到,协程中的输出和main中的输出只相差了1s,也就说明了为什么delay(1000L)是非阻塞的。delay()函数类似于Thread.sleep(),但是它不阻塞(non-blocking)线程,它是一个被suspend修饰的挂起函数,挂起函数只能被挂起函数调用或协程中调用。
suspend fun main(args: Array) {
val deferred = GlobalScope.async {
delay(1000L)
println("This is async ${TimeUtil.getTimeDetail()}")
return@async "taonce"
}
println("main start ${TimeUtil.getTimeDetail()}")
val result = deferred.await()
println("async result is $result")
println("main end ${TimeUtil.getTimeDetail()}")
}
// 输出
// main start 15:27:19:668
// This is async 15:27:20:652
// async result is taonce
// main end 15:27:20:657
async和launch参数是一模一样的,不同的是async返回的是Deferred对象,它继承了Job接口,所以说Job有的它都有,并且还额外增加了一个方法:
public suspend fun await(): T这个方法接收的是async闭包中返回的值。如果闭包中需要返回一个值那么我们就需要考虑用async了。
runBlocking的最大特点就是它的delay()可以阻塞当前的线程,和Thread.sleep()有着相同的效果。一般用runBlocking来桥接普通阻塞代码和挂起风格的非阻塞代码,在runBlocking闭包里面启动另外的协程。
以最常用的网络请求为例,如果不用Kotlin的协程来实现,我们可以用Thread、RxJava等等很多种方法。
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
coroutine.setOnClickListener { click() }
}
private fun click() = runBlocking {
GlobalScope.launch(Dispatchers.Main) {
coroutine.text = GlobalScope.async(Dispatchers.IO) {
// 比如进行了网络请求
// 放回了请求后的结构
return@async "main"
}.await()
}
}
}
上面代码的原理是:用async()在IO线程中去执行网络请求,然后通过await()返回请求结果,最后用launch(Dispatchers.Main)在主线程中更新UI。
其中用到了Dispatchers来指定协程所在的线程,目前Dispatchers有三种: