什么是流?
从流的方向来观察,我们称原始数据为上流,对数据进行一系列处理后,最终的数据为下流。
从流的属性来观察,我们认为生产者在上流生产数据,消费者在下流消费数据。
为什么引进Flow?
Flow 是 Kotlin 官方基于协程构建的用于响应式编程的API。响应式编程简单来说就是使用异步数据流进行编程 。协程中,使用挂起函数仅可以异步返回单个值,而 Flow 则可以异步返回多个值,并补全kotlin语言中响应式编程的空白。
Flow常见的操作
- 生产者消费者例子
suspend fun collect() {
flow {
//发射数据
emit(5)
}.collect {
//消费者
Log.i("minfo", "value=$it")
}
}
通过flow函数构造一个flow对象,然后通过调用flow.collect收集数据。
flow函数的闭包为生产者的生产逻辑,collect函数的闭包为消费者的消费逻辑。
流的三要素:原始数据、对数据的操作、最终数据,对应到Flow上也是一样的。
flow的闭包里我们看做是原始数据,而filter、map、catch等看做是对数据的操作,collect闭包里看做是最终的数据。
- 寻找1~1000内大于500,并且是7的倍数的数
suspend fun findNum() {
var flow = flow {
for (i in 1..1000) {
emit(i)
}
}.filter {
it > 500 && it % 7 == 0
}
flow.collect {
Log.i("minfo", "num=$it")
}
}
用Flow做倒计时功能:
var count = MutableLiveData(60)
fun countDown() {
viewModelScope.launch {
flow {
(60 downTo 0).forEach {
delay(1000)
emit(it)
}
}.flowOn(Dispatchers.Default)
.onStart {
// 倒计时开始
}
.onCompletion {
// 倒计时结束
}
.collect {
// 倒计时进行中
count.value = count.value?.minus(1)
}
}
}
- 实现一个计时器的效果,每秒钟更新一次时间
简单布局:
点击事件触发:
class MainActivity : AppCompatActivity() {
private lateinit var tvTime: TextView
private lateinit var btnStart: Button
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
tvTime = findViewById(R.id.tv_time)
btnStart = findViewById(R.id.btn_start)
btnStart.setOnClickListener {
timeFlow()
}
}
suspend fun timeFlow() {
fun timeFlow() {
var timeFlow = flow {
var time = 0
while (true) {
emit(time)
kotlinx.coroutines.delay(1000) //使用挂起函数delay
time++
}
}
}
}
现在的代码下,点击start,会不会就开始发送数据了,在这种场景下不会。因为使用flow构建函数构建出的Flow是属于Cold Flow,也叫做冷流。所谓冷流就是在没有任何接受端的情况下,Flow是不会工作的。只有在有接受端的情况下,Flow闭包中的代码就会自动开始执行。
修改代码让例子生效:
class MainActivity : AppCompatActivity() {
private lateinit var tvTime: TextView
private lateinit var btnStart: Button
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
tvTime = findViewById(R.id.tv_time)
btnStart = findViewById(R.id.btn_start)
btnStart.setOnClickListener {
GlobalScope.launch(Dispatchers.Main) {
timeFlow()
}
}
}
private suspend fun timeFlow() {
flow {
var time = 0
while (true) {
emit(time)
kotlinx.coroutines.delay(1000) //使用挂起函数delay
time++
}
}.collect {
tvTime.text = "$it"
}
}
}
使用collect函数进行数据接收,由于Flow的collect函数是一个挂起函数,因此必须在协程作用域或其他挂起函数中才能调用。
冷流和热流的区别:
冷流 | 热流 |
---|---|
不消费,不生产,多次消费,多次生产,只有1个观察者 | f有没有消费者都会生产数据 |
感知上游流的异常和启动
val sharedFlow = flow {
// 向下游发射100
emit(100)
// 向下游发射200
emit(200)
}.catch {
// 发生异常,发射-1通知下游
emit(-1)
}.onStart {
// 开始启动,向下游发射1
emit(1)
}
冷流转换热流的使用与原理
在协程中,通过调用操作符shareIn与stateIn,可以将一个冷流转换成一个热流,这两个方法的区别如下:
shareIn:将一个冷流转换成一个标准的热流——SharedFlow类型的对象。
stateIn:将一个冷流转换成一个单数据更新的热流——StateFlow类型的对象。
参考:
https://www.jianshu.com/p/a72c4637c546