0%

Kotlin协程

Kotlin协程学习

协程是什么

在Kotlin官方文档上面描述——“本质上,协程是轻量级的线程”,在Kotlin中的协程也可以说是Kotlin提供的一套线程封装的API。

一个简单的示例:

1
2
3
4
launch({
val user = APIService.getInstence().getUser() //请求网络数据
nameTv.text = user.name
})

在上面的代码中,我们可以看到第2行中做了一个请求网络数据的耗时的I/O操作。通过协程的方式,我们可以将这样的一个异步操作写得看起来和同步操作无异。

基本使用

示例中的launch函数并不是一个顶层函数,不能直接调用,需要被对象所调用。可以通过三种方法来创建协程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 使用runBlocking顶层函数
runBlocking{
getUserInfo(userId)
}

// 使用GlobalScope对象
GlobalScope.launch{
getUserInfo(userId)
}

// 创建新的CoroutineScope
val coroutineScope = CoroutineScope(context)
coroutineScope.launch{
getUserInfo(userId)
}
  • 方法一适用于单元测试,因为runBlocking中的挂起方法以及调用层级会一直有效的阻塞当前线程
  • 方法二不会阻塞当前显示,但是如果使用了GlobalScope,则该挂起方法的生命周期会和整个App一致,并且不能取消

launch方法中可以传入context参数,该参数和Android中的context不是同一概念,我们可以使用Android平台封装好的context,如Dispatchers.IODispatchers.MainDispatchers.Default,以及Dispatchers.Unconfined。在这里定义了对应的context,那么协程就会在该context上开启,比如在IO线程或者主线程中。

协程写法的好处

如果有这样一个需求,我们需要在一个方法体内同时获取用户的头像以及公司的信息,我们使用callback可能会这样写:

1
2
3
4
5
6
7
api.getAvatar(user.avatarUri){
avatar ->
api.getCompanyLogo(user.id){
logo ->
showMergeMessage(avatar, logo)
}
}

如果我们使用协程的写法:

1
2
3
4
5
coroutineScope.launch(Dispatchers.Main){
val avatar = async {api.getAvatar(user.avatarUri)}
val componyInfo = async { api.getCompanyInfo(user.id) }
showMergeMessage(avatar.await(), componyInfo.await())
}

这样对比可以看出,即使是复杂的并行网络请求,通过协程写出来的代码结构会更加清晰。

协程的正确使用

我们刚刚说过我们在launch方法中传入不同的context会决定该协程在哪个context上开启,那么刚才获取头像和公司信息的协程示例代码中,我们api请求应该需要放在IO线程中,而后面的showMergeMessage是需要将信息在页面上渲染,应该放在主线程中,类似于:

1
2
3
4
5
6
coroutineScope.launch(Dispatchers.IO){
val avatar = getAvatar()
launch(Dispatchers.Main){
avatarIv.setImageBitmap(avatar)
}
}

这样的话看起来确实好理解一些,但是还是有嵌套的代码。这里需要提到另一个函数——withContext。这个函数可以做一件事情——切换到指定的线程,在闭包中的逻辑执行结束后,自动切换回到之前的线程。如果我们把withContext放到getAvatar方法中,那么写法就变回了之前同步的写法。

结尾

文档内容是大部分引用于朱凯的关于协程的文章,做的一次学习总结。原文地址