持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第7天,点击查看活动详情
前言
Flow 是一种冷流,只有 collect 的时候才会产生数据,而 SharedFlow、StateFlow 虽然是热流,但是,每次 collect 的时候,要么是只能获取最新值,要么把之前存储的值重新都发一遍。有没有一种热流,能更像生产者与消费者,一个专门生产,一个专门消费,答案当然是有的,那便是 Channel。
Channel 的使用
GlobalScope.launch {
launch {
(1..3).forEach { value ->
println("channel 生产了 $value")
channel.send(value)
delay(100)
}
}
launch {
repeat(3){
delay(1000)
val value = channel.receive()
println("channel 消费了 $value")
}
}
}
复制代码
日志:
I/System.out: channel 生产了 1
I/System.out: channel 消费了 1
I/System.out: channel 生产了 2
I/System.out: channel 消费了 2
I/System.out: channel 生产了 3
I/System.out: channel 消费了 3
复制代码
容量
在这里,我特意让生产速度慢于消费速度,但是会发现,即使如此,还是生产一个,消费一个,这说明了 channel 内部是有容量的,所以需要等到消费者取走后才会生产继续生产,若我们需要扩大该容量,可以在创建 channel 的时候,赋予其初始容量:
val channel = Channel<Int>(2)
复制代码
参数说明
其实,容量的配置也只是其参数配置的一部分,下面我们来完整看看其说明:
public fun <E> Channel(
capacity: Int = RENDEZVOUS,
onBufferOverflow: BufferOverflow = BufferOverflow.SUSPEND,
onUndeliveredElement: ((E) -> Unit)? = null
): Channel<E>
复制代码
- capacity:容量配置,当其赋值大于等于 1 的时候,就是设置相应的容量,当其小于1 的时候,进行额外处理,并有相应的常数:
- RENDEZVOUS:默认值,值为 0,标识没有缓冲区。
- CONFLATED:值为 -1,创建一个 合并 channel。
- BUFFERED:值为 -2,对于支持挂起的 channel,设置其容量为 64,对于不支持挂起的 channel,其容量为 1。
- UNLIMITED:值为 Int.MAX_VALUE,设置为无限容量的 channel。
- onBufferOverflow:当超过缓冲区的时候,如何进行处理:
- SUSPEND:进行挂起操作。
- DROP_OLDEST:不挂起,直接移除最旧的值。
- DROP_LATEST:不挂起,直接移除最新的值。
- onUndeliveredElement:当生产数据后,因为其它原因导致未能交付给消费者,这些数据则会进入该函数。这样要特别注意,因为 DROP_OLDEST 和 DROP_LATEST 丢失的数据并不会进入该函数,一般为
cancel()
、close()
或者出现异常之类的,才会进入该函数。
下面,我们写个小栗子来说明这个参数的作用:
val channel = Channel<Int>(2, BufferOverflow.DROP_OLDEST){ value ->
println("$value 没有被正常消费")
}
GlobalScope.launch {
launch {
(1..5).forEach { value ->
println("channel 生产了 $value")
channel.send(value)
delay(100)
}
}
launch {
repeat(5){
delay(1000)
val value = channel.receive()
println("channel 消费了 $value")
}
}
}
复制代码
日志输出:
I/System.out: channel 生产了 1
I/System.out: channel 生产了 2
I/System.out: channel 生产了 3
I/System.out: channel 生产了 4
I/System.out: channel 生产了 5
I/System.out: channel 消费了 4
I/System.out: channel 消费了 5
复制代码
这里可以看出,生产了 1、2、3、4、5,但是只消费了 4、5,至于 1、2、3 都因为 DROP_OLDEST 被丢弃了,并且没有回调到 onUndeliveredElement。
我们稍微改下,看看另外一个🌰:
val channel = Channel<Int>(2, BufferOverflow.DROP_OLDEST){ value ->
println("$value 没有被正常消费")
}
GlobalScope.launch {
launch {
(1..5).forEach { value ->
println("channel 生产了 $value")
channel.send(value)
delay(100)
}
}
launch {
delay(1000)
channel.cancel()
}
}
复制代码
日志输出:
I/System.out: channel 生产了 1
I/System.out: channel 生产了 2
I/System.out: channel 生产了 3
I/System.out: channel 生产了 4
I/System.out: channel 生产了 5
I/System.out: 4 没有被正常消费
I/System.out: 5 没有被正常消费
复制代码
由于 cancel()
引起的关闭,会让 4、5 进入 onUndeliveredElement 回调中。