转载请标明出处:http://blog.csdn.net/zhaoyanjun6/article/details/117674222
本文出自【赵彦军的博客】
文章目录
依赖
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.1'
channels 可以干啥
channels
用于协程间的通信, 允许我们在不同的协程间传递数据(a stream of values).
数据模型
生产者-消费者模式
发送数据到channel
的协程被称为producer
, 从channel
接受数据的协程被称为consumer
.
当需要的时候, 多个协程可以向同一个channel
发送数据, 一个channel
的数据也可以被多个协程接收.
当多个协程从同一个channel
接收数据的时候, 每个元素仅被其中一个consumer
消费一次. 处理元素会自动将其从channel
里删除.
Channel的特点
Channel
在概念上有点类似于BlockingQueue
, 元素从一端被加入, 从另一端被消费. 关键的区别在于, 读写的方法不是blocking
的, 而是suspending
的.
在为空或为满时. channel
可以suspend它的send
和receive
操作.
生成数据,消费数据的方法都需要在协程里执行
Channel 分类
- RENDEZVOUS :表示约会形式的等待,没有缓存使用,send调用后就会一直挂起,直到receive到达。也是 Channel 默认形式。
- UNLIMITED:表示执行缓存无限容量,容量是
Int.MAX_VALUE
,send调用后就存放在channel里直接返回,不管是否有receive。但是我们在使用时还是需要注意内存情况。 - CONFLATED:表示保留最新,send调用后就存放在channel里直接返回,但是channel里只能存放最近一次 send 的值。
- BUFFERED:表示执行缓存使用默认容量,默认是64。
receive
在 Channel
有数据时接收数据,在 Channel
为空时处于挂起状态,时刻准备接收数据。一旦接收完数据后,就执行完了
class MainActivity : AppCompatActivity() {
private val channel = Channel<String>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
findViewById<View>(R.id.btn1).setOnClickListener {
//发送数据
GlobalScope.launch {
channel.send("item1 ${System.currentTimeMillis()}")
}
}
//接收数据
GlobalScope.launch {
repeat(10) {
Log.d("channel", "receive--${channel.receive()}")
}
}
}
consumeEach
class MainActivity : AppCompatActivity() {
private val channel = Channel<String>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
findViewById<View>(R.id.btn1).setOnClickListener {
//发送数据
GlobalScope.launch {
channel.send("item1 ${System.currentTimeMillis()}")
}
}
//接收数据
GlobalScope.launch {
channel.consumeEach {
Log.d("channel", "consumeEach--${it}")
}
}
}
}
//输出结果
D/channel: consumeEach--item1 1623123193526
consumeEach
会在接收到数据后,从把数据从队列中移除。其他接收者,就不会再次接收了。也就是同一个数据,只能被一个数据接收
遍历
kotlin
为我们提供了一个简单的 channel
的遍历方法,也就是 for
循环。
遍历 channel
会一直处于挂起状态,只要有数据,就会全部遍历一次。没有数据,处于挂起状态。
class MainActivity : AppCompatActivity() {
private val channel = Channel<String>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
findViewById<View>(R.id.btn1).setOnClickListener {
//发送数据
GlobalScope.launch {
repeat(100) {
channel.send("item1 $it")
}
}
}
//接收数据
GlobalScope.launch {
for (i in channel) {
Log.d("channel", "$i")
}
}
}
}
close
不同于Queue
,channel
可以被关闭,对于channel
的关闭,我们可以使用close()
,关闭前发射的值将仍然能在接收端收到,接收端通过for
循环来遍历接收到的值。
如果执行了 close 以后,还调用 send 方法,就会崩溃
所以一种比较安全的做法是,在 send
之前,要判断一下channel
是否已经关闭了
//发送数据
GlobalScope.launch {
if (!channel.isClosedForSend) {
channel.send("item1 ${System.currentTimeMillis()}")
}
}
Channel的协程Buidler(SendChannel / ReceiveChannel)
我们在上面示例中可见,通过一个生产者协程producer和一个消费者协程consumer进行了数据的send和receive,而在官方框架中也专门为生产者协程和消费者协程提供了两个函数来构建出协程,它们就是produce和actor。
而且通过produce和actor函数启动的协程结束后都会自动关闭对应的Channel。
-
produce:启动一个生产者协程,返回ReceiveChannel。
-
actor:启动一个消息者协程,返回SendChannel(注意,actor函数目前框架中是被标为废弃)。
示例如下:
BroadcastChannel
前面介绍的Channel的所发送的数据只能被一个消费者消费,而如果需要一对多的话那就需要BroadcastChannel,它会像我们平时使用广播一样进行分发给所有订阅者。
另外需要注意的是,BroadcastChannel不支持RENDEZVOUS。
Select
Select一般是IO多路复用的概念,而在协程的Select则是用于挂起函数的多路复用。通俗一点表达就是可以同时进行多个挂起函数的调用,但最后只选择执行最快的挂起函数的返回结果。
其他方法
channel.isClosedForSend //发送通道是否关闭
channel.isClosedForReceive //接收通道是否关闭
channel.isEmpty //是否为空