kotlin协程 Flow数据流

本文详细介绍了Kotlin协程中的Flow概念,以及其与RxJava的关系,展示了Flow的常见操作符如map,flatMap,flatMapMerge,flatMapLatest的使用,以及它们在处理冷流和热流时的区别。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

flow简介

Flow是kotlin协程中的流。RxJava就是流式编程的库。Flow属于冷流对应RxJava中的Observable Flowable Single MayBe和Completable等。Kotlin协程中的热流实现MutableSharedFlow和MutableStateFlow等,对应RxJava中热流PublisherSubject和BehaviorSubject。

Flow用法

fun main() {
    runBlocking (Dispatchers.Default){
        // 发送10个元素,从0到9
        val myFlow = flow {
            repeat(10){
                emit(it)
            }
        }
        launch {
            myFlow.collect{
                println("Coroutine1:$it")
            }
        }
        launch {
            myFlow.collect{
                println("Coroutine2:$it")
            }
        }
    }
}

协程1和2通过Flow.collect订阅Flow

fun main() {
    runBlocking (Dispatchers.Default){
        // 发送10个元素,从0到9
        val myFlow = flow {
            repeat(10){
                // 修改原来的CoroutineContext,会异常
                withContext(Dispatchers.IO){
                    emit(it)
                }
            }
        }
        launch {
            myFlow.collect{
                println("Coroutine1:$it")
            }
        }
    }
}

Flow限制,不能修改原来的CoroutineContext。可以使用ChannelFlow就能正常使用。 

fun main() {
    runBlocking (Dispatchers.Default){
        // 发送10个元素,从0到9
        val myFlow = channelFlow {
            repeat(10){
                // 可以修改原来的CoroutineContext
                withContext(Dispatchers.IO){
                    channel.send(it)
                }
            }
        }
        launch {
            myFlow.collect{
                println("Coroutine1:$it")
            }
        }
    }
}
fun main() {
    runBlocking(Dispatchers.Default) {
        // 发送10个元素,从0到9
        val myFlow = flow {
            repeat(10) {
                try {
                    emit(it)
                } catch (e: Throwable) {
                    emit(22)
                }
            }
        }
        launch {
            myFlow.collect {
                if (it == 2) {
                    // 这里出现异常后,collect订阅就结束了(只打印2次,第三次就异常了)
                    error("Error")
                }
                println("Coroutine1:$it")
            }
        }
    }
}

 Flow中collect异常,那么订阅就结束了。

Flow工作原理

public fun <T> flow(@BuilderInference block: suspend FlowCollector<T>.() -> Unit): Flow<T> = SafeFlow(block)
private class SafeFlow<T>(private val block: suspend FlowCollector<T>.() -> Unit) : AbstractFlow<T>() {
    override suspend fun collectSafely(collector: FlowCollector<T>) {
        collector.block()
    }
}

public final override suspend fun collect(collector: FlowCollector<T>) {
    val safeCollector = SafeCollector(collector, coroutineContext)
    try {
        collectSafely(safeCollector)
    } finally {
        safeCollector.releaseIntercepted()
    }
}

创建SafeFlow,继承于AbstractFlow,订阅调用的是collect。检查当前CoroutineContext和调用的collect方法传入的是否一致,不一致就抛出异常。Flow被SafeCollector代理去检查异常。转换前的流称上游Upstream,处理后再发送到下游Downstream。

flatMap操作符

 类似于RxJava中的concatMap操作符

fun main() {
    runBlocking(Dispatchers.Default) {
        val myFlow = flow {
            repeat(3) {
                emit(it)
            }
        }
        launch {
            // 将原来的流元素构建成一个新的流(按照原来的流元素输出)
            myFlow.flatMapConcat { upstreamValue ->
                flow {
                    delay(1000L - upstreamValue * 100)
                    repeat(2) {
                        emit(upstreamValue * 10 + it)
                    }
                }
            }.collect {
                println("collect $it")
            }
        }
    }
}
输出:
collect 0
collect 1
collect 10
collect 11
collect 20
collect 21

将原来发送3个元素,通过flatMapConcat()发送两个元素。越是先发送的元素延迟时间越长,然后按顺序输出6个元素。

flatMapMerge

类似于RxJava中的flatMap 

fun main() {
    runBlocking(Dispatchers.Default) {
        val myFlow = flow {
            repeat(3) {
                emit(it)
            }
        }
        launch {
            // 将原来的流元素构建成一个新的流(默认并发16个)谁先执行完就发送谁
            myFlow.flatMapMerge { upstreamValue ->
                flow {
                    delay(1000L - upstreamValue * 100)
                    repeat(2) {
                        emit(upstreamValue * 10 + it)
                    }
                }
            }.collect {
                println("collect $it")
            }
        }
    }
}
输出:
collect 20
collect 21
collect 10
collect 11
collect 0
collect 1

不会保证原来的顺序,哪个流先处理完就先发送数据。concurrency默认值16,并行执行的数量。当concurrency为1时和flatMapConcat一样。  

fun main() {
    runBlocking(Dispatchers.Default) {
        val myFlow = flow {
            repeat(3) {
                emit(it)
            }
        }
        launch {
            // 将原来的流元素构建成一个新的流(并发数是2,达到2个的时候等待然后再执行下一个)
            myFlow.flatMapMerge(2) { upstreamValue ->
                flow {
                    delay(1000L - upstreamValue * 100)
                    repeat(2) {
                        emit(upstreamValue * 10 + it)
                    }
                }
            }.collect {
                println("collect $it")
            }
        }
    }
}
输出:
collect 10
collect 11
collect 0
collect 1
collect 20
collect 21

flatMapLatest

类似于RxJava中的switchMap

fun main() {
    runBlocking(Dispatchers.Default) {
        val myFlow = flow {
            repeat(3) {
                emit(it)
            }
        }
        launch {
            // 前面没执行完的Flow会被取消,然后被后续的Flow替换
            myFlow.flatMapLatest  { upstreamValue ->
                flow {
                    delay(1000L - upstreamValue * 100)
                    repeat(2) {
                        emit(upstreamValue * 10 + it)
                    }
                }
            }.collect {
                println("collect $it")
            }
        }
    }
}
输出:
collect 20
collect 21

总结

上面介绍了Flow常用的操作符map flatMap(串式) ,flatMapMerge(并发) ,flatmapLatest(取代旧的)等简单使用。

Kotlin 协程中,`withContext` 是一个非常重要的函数,用于在不改变协程的生命周期或取消行为的前提下切换执行上下文。它通常用于在不同 `Dispatcher` 之间切换线程,例如从主线程切换到后台线程执行耗时任务,然后再返回主线程更新 UI。 ### 使用场景与方式 `withContext` 的典型用法是在 `Flow` 或普通协程体中执行阻塞或计算密集型操作时避免阻塞主线程。例如: ```kotlin val result = withContext(Dispatchers.IO) { // 执行网络请求、数据库查询等操作 fetchDataFromNetwork() } ``` 在 `Flow` 中,虽然 `flow` 构建器本身支持使用 `flowOn` 来指定上游操作的上下文,但在某些情况下仍然需要显式使用 `withContext` 来控制特定代码块的执行线程[^1]。 ### 最佳实践 #### 1. **避免滥用** - 在 Flow 中优先使用 `flowOn` 而不是 `withContext`:`flowOn` 更适合用于声明整个流链的上下文切换,而 `withContext` 更适合于局部代码块的上下文切换。 - 如果你在一个 `Flow` 操作符内部执行耗时任务,可以考虑将整个流链通过 `flowOn(Dispatchers.IO)` 指定执行上下文,而不是在每个中间处理步骤都使用 `withContext` [^1]。 #### 2. **选择合适的 Dispatcher** - 根据任务类型选择合适的 `CoroutineDispatcher`: - `Dispatchers.Main`:用于 UI 更新和与主线程交互的操作。 - `Dispatchers.IO`:适用于 IO 密集型任务,如文件读写、网络请求。 - `Dispatchers.Default`:适用于 CPU 密集型任务,如数据转换、图像处理。 #### 3. **性能优化** - 避免频繁地切换上下文。如果多个操作可以共享相同的上下文,则应合并它们以减少上下文切换带来的开销。 - 注意不要在 `withContext` 内部执行可能引发内存泄漏的操作,尤其是在 Android 开发中涉及 Activity 或 Fragment 的引用时。 #### 4. **取消与异常处理** - `withContext` 块会继承当前协程的取消状态。如果在其执行过程中协程被取消,`withContext` 将抛出 `CancellationException`。 - 确保在 `try-catch` 块中捕获异常以防止协程意外终止,尤其是在执行不可靠的外部调用(如网络请求)时。 示例代码如下: ```kotlin viewModelScope.launch { val data = try { withContext(Dispatchers.IO) { // 模拟网络请求 delay(1000) "Data from network" } } catch (e: Exception) { // 处理异常 "Error" } // 更新 UI textView.text = data } ``` #### 5. **结合 Flow 使用** - 在 `Flow` 中使用 `withContext` 时,需注意其作用范围仅限于该代码块,不会影响后续操作符的执行上下文。若希望整个流链都在某个上下文中运行,建议使用 `flowOn`: ```kotlin flow { emit(fetchData()) // 在当前上下文中执行 } .map { withContext(Dispatchers.Default) { // 数据处理逻辑 processData(it) } } .flowOn(Dispatchers.IO) .launchAndCollectIn(lifecycleScope, Lifecycle.State.STARTED) ``` 在这个例子中,`fetchData()` 在 `Dispatchers.IO` 上运行,而 `processData` 则在 `Dispatchers.Default` 上运行,展示了如何灵活组合不同上下文以实现高效的并发处理。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值