Kotlin实战指南二十:flow

转载请标明出处:http://blog.csdn.net/zhaoyanjun6/article/details/117370700
本文出自【赵彦军的博客】

往期精彩文章

Kotlin实战指南十九:use 函数魔法
Kotlin实战指南十八:open、internal 关键字使用
Kotlin实战指南十七:JvmField、JvmStatic使用
Android SharedFlow详解
Android StateFlow详解

flow 是啥

按顺序发出值并正常完成或异常完成的冷流异步数据流

flow咋用?

flow 是kotlin coroutines 库里面的类,所以使用 flow 之前,要确保添加了协程依赖

implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.0"

引入依赖后,我们写一段代码:

       flow {
            emit(1)  //发射数字 1
            emit(2)  //发射数字 2
        }.collect {
            //接收结果
            Log.d("flow-", "value $it")
        }

如果你这样写就会报错
在这里插入图片描述
意思是:collect 方法是 suspend 修饰的挂起函数,只能在协程里,或者其他挂起函数中使用。我们来修改一下:

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        GlobalScope.launch {
            flow {
                emit(1)  //发射数字 1
                emit(2)  //发射数字 2
            }.collect {
                //接收结果
                Log.d("flow-", "value: $it")
            }
        }
    }
}

输出结果:

D/flow-: value: 1
D/flow-: value: 2

创建flow

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        GlobalScope.launch {
            val flow = flow {
                emit(1)
                emit(2)
            }

            val flow2 = flowOf(1, 2.3)
            val flow3 = mutableListOf(1, 2, 3, 4, 5).asFlow()
            val flow4 = (1..4).asFlow()
        }
    }
}

map 操作符

例子1

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        GlobalScope.launch {
            flow {
                emit(1)  //发射数字 1
                emit(2)  //发射数字 2
            }.map {
                "map $it"
            }.collect {
                //接收结果
                Log.d("flow-", "value: $it")
            }
        }
    }
}

输出结果:

D/flow-: value: map 1
D/flow-: value: map 2

例子2

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val flow: Flow<String> = flow {
            emit(1)  //发射数字 1
            emit(2)  //发射数字 2
        }.map {
            "map $it"
        }

        GlobalScope.launch {
            flow.collect {
                //接收结果
                Log.d("flow-", "value: $it")
            }
        }
    }
}
输出结果:
```java
D/flow-: value: map 1
D/flow-: value: map 2

在接收数据的时候,我们用了 collect 方法,collect 英文含义就是收集的意思

catch 捕获上游出现的异常

当 flow 流操作中发生异常的情况时,程序会发生崩溃:

例子1:不捕获异常

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val flow: Flow<String> = flow {
            emit(1)  //发射数字 1
            emit(2)  //发射数字 2
        }.map {
            0 / 0  //人为制造异常
            "map $it"
        }

        GlobalScope.launch {
            flow
                .collect {
                    //接收结果
                    Log.d("flow-", "value: $it")
                }
        }
    }
}

输出结果:程序发生崩溃
在这里插入图片描述

例子2:捕获异常

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val flow: Flow<String> = flow {
            emit(1)  //发射数字 1
            emit(2)  //发射数字 2
        }.map {
            0 / 0  //人为制造异常
            "map $it"
        }

        GlobalScope.launch {
            flow
                .catch {
                    //捕获异常
                    Log.d("flow-", "value: ${it.message}")
                }.collect {
                    //接收结果
                    Log.d("flow-", "value: $it")
                }
        }
    }
}

输出结果:

D/flow-: exception: divide by zero

可以看到程序可以正常运行,我们也正常捕获了异常

取消flow

Flow创建后并不返回可以cancel的句柄,但是一个flow的collect是suspend的,所以可以像取消一个suspend方法一样取消flow的collection。

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val job = GlobalScope.launch {
            flow {
                emit(1)
                kotlinx.coroutines.delay(3000)
                emit(2)
                kotlinx.coroutines.delay(3000)
                emit(3)
            }
                .collect {
                    //接收结果
                    Log.d("flow-", "value: $it")
                }
        }

        findViewById<Button>(R.id.cancel).setOnClickListener {
            job.cancel()
        }
    }
}

collectIndexed

输出带有索引的结果
在这里插入图片描述

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        GlobalScope.launch {
            flow {
                Log.d("flow-", "init")
                emit(1)
                emit(2)
            }.collectIndexed { index, value ->
                Log.d("flow-", "onEach $index $value")
            }
        }
    }
}

//输出结果
D/flow-: init
D/flow-: onEach 0 1
D/flow-: onEach 1 2

distinctUntilChanged 过滤重复的值

如果生产的值和上个发送的值相同,值就会被过滤掉

flow {
    emit(1)
    emit(1)
    emit(2)
    emit(2)
    emit(3)
    emit(4)
}.distinctUntilChanged()

// 结果:1 2 3 4
// 解释:
// 第一个1被释放
// 第二个1由于和第一个1相同,被过滤掉
// 第一个2被释放
// 第二个2由于和第一个2相同,被过滤掉
// 第一个3被释放
// 第一个4被释放

可以传参(old: T, new: T) -> Boolean,进行自定义的比较

private class Person(val age: Int, val name: String)

flow {
    emit(Person(20, "张三"))
    emit(Person(21, "李四"))
    emit(Person(21, "王五"))
    emit(Person(22, "赵六"))
}.distinctUntilChanged{old, new -> old.age == new.age }
.collect{ value -> println(value.name) }
    
// 结果:张三 李四 赵六
// 解释:本例子定义如果年龄相同就认为是相同的值,所以王五被过滤掉了

可以用 distinctUntilChangedBy转换成年龄进行对比

flow {
    emit(Person(20, "张三"))
    emit(Person(21, "李四"))
    emit(Person(21, "王五"))
    emit(Person(22, "赵六"))
}.distinctUntilChangedBy { person -> person.age }

// 结果:张三 李四 赵六

transform

对每个值进行转换,用一个新的 FlowCollector 发射数据

在这里插入图片描述

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        GlobalScope.launch {
            flow {
                emit(1)
                emit(2)
                emit(3)
                emit(4)
                emit(5)
            }
                .transform {
                    if (it % 2 == 0) {
                        emit("value-$it")
                    }
                }
                .collect {
                    Log.d("flow-", "collect $it")
                }
        }
    }
}

//输出结果
D/flow-: collect value-2
D/flow-: collect value-4

filter 过滤

val list = mutableListOf(1, 2, 3, 4, 5)

val job = GlobalScope.launch {
    list.asFlow()
        .filter {
           it > 3
         }
        .collect {
           Log.d("flow-", "value: $it")
        }
}

输出结果:

 D/flow-: value: 4
 D/flow-: value: 5

类似的还有 filterNot 反向过滤,这里就不举例子了

flowOn 数据发射的线程

可以切换CoroutineContext

说明:flowOn只影响该运算符之前的CoroutineContext,对它之后的CoroutineContext没有任何影响
我们先看一下默认情况下的线程问题

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val flow = flow {
            Log.d("flow-", "init->thread:${Thread.currentThread().name}")
            emit(1)
            emit(2)
        }

        val job = GlobalScope.launch {
            flow
                .collect {
                    Log.d("flow-", "collect->thread:${Thread.currentThread().name} value: $it")
                }
        }
    }
}

输出结果

D/flow-: init->thread:DefaultDispatcher-worker-1
D/flow-: collect->thread:DefaultDispatcher-worker-1 value: 1
D/flow-: collect->thread:DefaultDispatcher-worker-1 value: 2

我们可以看到默认情况下,flow 数据发射的线程就是当前协程所在的线程。

我们可以用 flowOn 方法指定数据发射的线程


class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val flow = flow {
            Log.d("flow-", "init->thread:${Thread.currentThread().name}")
            emit(1)
            emit(2)
        }

        GlobalScope.launch {
            flow
                .flowOn(Dispatchers.Main) //指定数数据发射的线程为主线程
                .collect {
                    Log.d("flow-", "collect->thread:${Thread.currentThread().name} value: $it")
                }
        }
    }
}

输出结果

D/flow-: init->thread:main
D/flow-: collect->thread:DefaultDispatcher-worker-2 value: 1
D/flow-: collect->thread:DefaultDispatcher-worker-2 value: 2

可以看到数据发射的线程已经被切换到了主线程

多个操作符的情况下,如下

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        GlobalScope.launch {
            flowDemo()
        }
    }

    suspend fun flowDemo() {
        flow {
            Log.d("flow-", "init ${Thread.currentThread().name}")
            emit(1)
        }.map {
            Log.d("flow-", "map ${Thread.currentThread().name}")
            it
        }.flowOn(Dispatchers.Main)
            .collect {
                Log.d("flow-", "collect ${Thread.currentThread().name} value:$it")
            }
    }
}
D/flow-: init main
D/flow-: map main
D/flow-: collect DefaultDispatcher-worker-1 value:1

launchIn

scope.launch { flow.collect() }的缩写, 代表在某个协程上下文环境中去接收释放的值
在这里插入图片描述

例子:

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        GlobalScope.launch {
            val flow = flow {
                Log.d("flow-", "init")
                emit(1)
                emit(2)
            }.onEach {
                Log.d("flow-", "onEach $it")
            }
            flow.launchIn(this)
        }
    }
}

输出结果:

D/flow-: init
D/flow-: onEach 1
D/flow-: onEach 2

conflate()

如果值的生产速度大于值的消耗速度,就忽略掉中间未来得及处理的值,只处理最新的值。

val flow1 = flow {
    delay(2000)
    emit(1)
    delay(2000)
    emit(2)
    delay(2000)
    emit(3)
    delay(2000)
    emit(4)
}.conflate()

flow1.collect { value ->
    println(value)
    delay(5000)
}

// 结果: 1 3 4
// 解释:
// 2000毫秒后生产了1这个值,交由collect执行,花费了5000毫秒,当1这个值执行collect完成后已经经过了7000毫秒。
// 这7000毫秒中,生产了2,但是collect还没执行完成又生产了3,所以7000毫秒以后会直接执行3的collect方法,忽略了2这个值
// collect执行完3后,还有一个4,继续执行。

withIndex

将值封装成IndexedValue对象
在这里插入图片描述
在这里插入图片描述

flow {
    emit(1)
    emit(2)
    emit(3)
    emit(4)
}.withIndex()

// 结果:
// I/System.out: IndexedValue(index=0, value=1)
// I/System.out: IndexedValue(index=1, value=2)
// I/System.out: IndexedValue(index=2, value=3)
// I/System.out: IndexedValue(index=3, value=4)

onEach

每个值释放的时候可以执行的一段代码

在这里插入图片描述
onEach 函数,执行一段代码后,再释放值

flow {
    emit(1)
    emit(2)
    emit(3)
    emit(4)
}.onEach { println("接收到$it") }

// 结果:
I/System.out: 接收到1
I/System.out: 1
I/System.out: 接收到2
I/System.out: 2
I/System.out: 接收到3
I/System.out: 3
I/System.out: 接收到4
I/System.out: 4

onStart 在数据发射之前触发

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val flow = flow {
            Log.d("flow-", "init->thread:${Thread.currentThread().name}")
            emit(1)
            emit(2)
            emit(3)
        }

        GlobalScope.launch {
            flow
                .onStart {
                    Log.d("flow-", "onStart->thread:${Thread.currentThread().name}")
                }
                .flowOn(Dispatchers.Main) //指定数数据发射的线程为主线程
                .collect {
                    Log.d("flow-", "collect->thread:${Thread.currentThread().name} value: $it")
                }
        }
    }
}

输出结果:

D/flow-: onStart->thread:main
D/flow-: init->thread:main
D/flow-: collect->thread:DefaultDispatcher-worker-1 value: 1
D/flow-: collect->thread:DefaultDispatcher-worker-1 value: 2
D/flow-: collect->thread:DefaultDispatcher-worker-1 value: 3

结论:
onStart 方法在数据发射之前调用,onStart 所在的线程是数据产生的线程。

onCompletion

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        GlobalScope.launch {
            val flow = flow {
                Log.d("flow-", "init ${Thread.currentThread().name}")
                emit(1)
                emit(2)
            }

            flow
                .onStart {
                    Log.d("flow-", "onStart ${Thread.currentThread().name}")
                }
                .onCompletion {
                    Log.d("flow-", "onCompletion ${Thread.currentThread().name}")
                }.collect {
                    Log.d("flow-", "collect ${Thread.currentThread().name} value:$it")
                }
        }
    }
}

输出结果:

D/flow-: onStart DefaultDispatcher-worker-1
D/flow-: init DefaultDispatcher-worker-1
D/flow-: collect DefaultDispatcher-worker-1 value:1
D/flow-: collect DefaultDispatcher-worker-1 value:2
D/flow-: onCompletion DefaultDispatcher-worker-1

drop(n) 忽略最开始释放的值

flow {
    emit(1)
    emit(2)
    emit(3)
    emit(4)
}.drop(2)

// 结果:3 4
// 解释:
// 最开始释放的两个值(1,2)被忽略了

dropWhile

判断第一个值如果满足(T) -> Boolean这个条件就忽略

flow {
    emit(1)
    emit(2)
    emit(3)
    emit(4)
}.dropWhile {
    it % 2 == 0
}

// 结果:1 2 3 4
// 解释:
// 第一个值不是偶数,所以1被释放

flow {
    emit(1)
    emit(2)
    emit(3)
    emit(4)
}.dropWhile {
    it % 2 != 0
}

// 结果:2 3 4
// 解释:
// 第一个值是偶数,所以1被忽略

sample

如果有一個資料來源大概每200ms 就會丟出來数据,但是在更新UI 的时候,我们不需要那么频繁的更新UI , 就需要用到采样。比如每 2 秒更新一次。
在这里插入图片描述

take 取指定数量的数据

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val flow = flow {
            emit(1)
            emit(2)
            emit(3)
        }

        GlobalScope.launch {
            flow
                .take(2)
                .flowOn(Dispatchers.Main) //指定数数据发射的线程为主线程
                .collect {
                    Log.d("flow-", "collect->thread:${Thread.currentThread().name} value: $it")
                }
        }
    }
}

输出结果:

D/flow-: collect->thread:DefaultDispatcher-worker-2 value: 1
D/flow-: collect->thread:DefaultDispatcher-worker-2 value: 2

takeWhile

只会释放第一个值,但也不是必须释放,要看条件
在这里插入图片描述

判断第一个值如果满足(T) -> Boolean这个条件就释放

flow {
    emit(1)
    emit(2)
    emit(3)
    emit(4)
}.takeWhile { it%2 != 0 }

// 结果:1
// 解释:
// 第一个值满足是奇数条件

flow {
    emit(1)
    emit(2)
    emit(3)
    emit(4)
}.takeWhile { it%2 == 0 }

// 结果:无
// 解释:
// 第一个值不满足是奇数条件

debounce()

Kotlin Flow中的 debounce 操作符用于限制流中的事件传递速率。它会等待一段时间,如果在该时间段内没有新的事件到达,则发射最后一个事件;如果在该时间段内有新的事件到达,则重新等待一段时间。这对于处理频繁触发的事件流非常有用,例如用户输入搜索框的文本变化事件。

debounce操作符可以用于减少不必要的事件处理,以提高性能。它可以防止处理过于频繁的事件,特别是在用户输入等场景下,避免不必要的网络请求或计算操作。

debounce函数可以用来确保flow的各项数据之间存在一定的时间间隔,如果是时间点过于临近的数据只会保留最后一条。

这个函数在某些特定场景下特别有用。

想象一下我们正在Edge浏览器的地址栏中输入搜索关键字,浏览器的地址栏下方通常都会给出一些搜索建议。

debounce( 3000 ) : 3000 毫秒内,取最后一个值。

@FlowPreview
public fun <T> Flow<T>.debounce(timeoutMillis: Long): Flow<T> {
    require(timeoutMillis >= 0L) { "Debounce timeout should not be negative" }
    if (timeoutMillis == 0L) return this
    return debounceInternal { timeoutMillis }
}

举例:

class Te {

    private val stateFlow = MutableStateFlow(1)
    private val scope = MainScope()

    fun test() {
        scope.launch {
            stateFlow
                .debounce(1000)
                .collect {
                    println("stateFlow 接收:$it")
                }
        }

        scope.launch {
            delay(300)
            stateFlow.value = 2
            delay(300)
            stateFlow.value = 3
            delay(300)
            stateFlow.value = 4
            delay(300)
            stateFlow.value = 5
            delay(300)
            stateFlow.value = 6
            delay(300)
            stateFlow.value = 7
            delay(300)
            stateFlow.value = 8
            delay(300)
            stateFlow.value = 9
        }
    }
}

日志:

System.out: stateFlow 接收:9

实现一个定时器功能

GlobalScope.launch {
      val flow = flow {
          while (true) {
              emit(0)
              //每隔一秒产生一个数据
              delay(1000)
         }
      }
      flow.collect {
          UtilLog.d(TAG, "定时任务..")
           
      }
}
  • 4
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值