Kotlin协程之Flow触发与消费示例解析

目录
  • 示例
  • 一.Flow的触发与消费
    • 1.onEach方法
    • 2.transform方法
    • 3.collect方法
  • 二.多消费过程的执行
  • 三.总结

示例

代码如下:

launch(Dispatchers.Main) {
    val task = flow {
        emit(2)
        emit(3)
    }.onEach {
        Log.d("liduo", "$it")
    }
    task.collect()
}

一.Flow的触发与消费

在Kotlin协程:Flow基础原理的分析中,流的触发与消费都是同时进行的。每当调用collect方法时,会触发流的执行,并同时在collect方法中对流发出的值进行消费。

而在协程中,其实还提供了分离流的触发与消费的操作——onEach方法。通过使用onEach方法,可以将原本在collect方法中的消费过程的移动到onEach方法中。这样在构建好一个Flow对象后,不会立刻去执行onEach方法,只有当调用collect方法时,才会真正的去触发流的执行。这样就实现了流的触发与消费的分离。

接下来,将对onEach方法进行分析。

1.onEach方法

onEach方法用于预先构建流的消费过程,只有在触发流的执行后,才会对流进行消费,代码如下:

public fun <T> Flow<T>.onEach(action: suspend (T) -> Unit): Flow<T> = transform { value ->
    action(value)
    return@transform emit(value)
}

onEach方法是一个Flow接口的扩展方法,返回一个类型为Flow的对象。Flow方法内部通过transform方法实现。

2.transform方法

transform方法是onEach方法的核心实现,代码如下:

public inline fun <T, R> Flow<T>.transform(
    @BuilderInference crossinline transform: suspend FlowCollector<R>.(value: T) -> Unit
): Flow<R> = flow { // 创建Flow对象
    collect { value -> // 触发collect
        return@collect transform(value)
    }
}

transform方法也是Flow接口的扩展方法,同样会返回一个类型为Flow的对象。并且在transform方法内部,首先构建了一个类型为Flow的对象,并且在这个Flow对象的执行体内,调用上游的流的collect来触发消费过程,并通过调用参数transform来实现消费。这个collect方法是一个扩展方法,在Kotlin协程:Flow基础原理分析过,因此不再赘述。

这就是onEach方法实现触发与消费分离的核心,它将对上游的流的消费过程包裹在了一个新的流内,只有当这个新的流或其下游的流被触发时,才会触发这个新的流自身的执行,从而实现对上游的流的消费。

接下来分析一下流的消费过程。

3.collect方法

collect方法用于触发流的消费,我们这里调用的collect方法,是一个无参数的方法,代码如下:

public suspend fun Flow<*>.collect(): Unit = collect(NopCollector)

这里的无参数collect方法是Flow接口的扩展方法。在无参数collect方法中,调用了另一个有参数的collect方法,这个有参数的collect方法在Kotlin协程:Flow基础原理中提到过,就是Flow接口中定义的方法,并且传入了NopCollecor对象,代码如下:

internal object NopCollector : FlowCollector<Any?> {
    override suspend fun emit(value: Any?) {
        // 什么都不做
    }
}

NopCollecor是一个单例类,它实现了FlowCollector接口,但是emit方法为空实现。

因此,这里会调用onEach方法返回的Flow对象的collect方法,这部分在Kotlin协程:Flow基础原理进行过分析,最后会触发flow方法中的block参数的执行。而这个Flow对象就是transform方法返回的Flow对象。代码如下:

public inline fun <T, R> Flow<T>.transform(
    @BuilderInference crossinline transform: suspend FlowCollector<R>.(value: T) -> Unit
): Flow<R> = flow { // 创建Flow对象
    collect { value -> // 触发collect
        return@collect transform(value)
    }
}

通过上面的transform方法可以知道,在触发flow方法中的block参数执行后,会调用collect方法。上面提到transform方法是Flow接口的扩展方法,因此这里有会继续调用上游Flow对象的collect方法。这个过程与刚才分析的类似,这里调用的上游的Flow对象,就是我们在示例代码中通过flow方法构建的Flow对象。

此时,会触发上游flow方法中block参数的执行,并在执行过程中,通过emit方法将值发送到下游。

接下来,在transform方法中,collect方法的block参数会被会被回调执行,处理上游发送的值。这里又会继续调用transform方法中参数的执行,这部分逻辑在onEach方法中,代码如下:

public fun <T> Flow<T>.onEach(action: suspend (T) -> Unit): Flow<T> = transform { value ->
    action(value)
    return@transform emit(value)
}

这里会调用参数action的执行,流在这里最终被消费。同时,onEach方法会继续调用emit方法,将上游返回的值再原封不动的传递到下游,交由下游的流处理。

二.多消费过程的执行

首先看下面这段代码:

launch(Dispatchers.Main) {
    val task = flow {
        emit(2)
        emit(3)
    }.onEach {
        Log.d("liduo1", "$it")
    }.onEach {
        Log.d("liduo2", "$it")
    }
    task.collect()
}

根据上面的分析,两个onEach方法会按顺序依次执行,打印出liduo1:2、liduo2:2、liduo1:3、liduo2:3。就是因为onEach方法会将上游的值继续向下游发送。

同样的,还有下面这段代码:

launch(Dispatchers.Main) {
    val task = flow {
        emit(2)
        emit(3)
    }.onEach {
        Log.d("liduo1", "$it")
    }
    task.collect {
        Log.d("liduo2", "$it")
    }
}

这段代码也会打印出liduo1:2、liduo2:2、liduo1:3、liduo2:3。虽然使用了onEach方法,但也可以调用有参数的collect方法来对上游发送的数据进行最终的处理。

三.总结

粉线为代码编写顺序,绿线为下游触发上游的调用顺序,红线为上游向下游发送值的调用顺序,蓝线为onEach方法实现的核心。

到此这篇关于Kotlin协程之Flow触发与消费示例解析的文章就介绍到这了,更多相关Kotlin Flow触发与消费内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Kotlin 挂起函数CPS转换原理解析

    目录 正文 1.什么是CPS转换 2.CPS的过程是怎么让参数改变的 3.CPS的过程是怎么让返回值改变的 4.挂起函数的反编译 5.非挂起函数的分析 正文 普通函数加上suspend之后就成为了一个挂起函数,Kotlin编译器会将这个挂起函数转换成了带有参数Continuation<T>的一个普通函数,Continuation是一个接口,它跟Java中的Callback有着一样的功能,这个转换过程被称为CPS转换. 1.什么是CPS转换 挂起函数中的CPS转换就是把挂起函数转换成一个带有Ca

  • Kotlin扩展函数与运算符重载超详细解析

    目录 一.扩展函数 二.运算符重载 一.扩展函数 不少现代高级编程语言中有扩展函数这个概念,Java却一直以来都不支持这个功能,Kotlin对扩展函数有了很好的支持. 扩展函数表示即使在不修改某个类的源码的情况下,仍然可以打开这个类,向该类添加新的函数. 比如有一个功能:一段字符串中可能包含字母.数字和特殊符号等字符,现在我们希望统计字符串中字母的数量,要怎么实现这个功能?如果按照一般的编程思维,可能会很自然的写出如下函数: object StringUtil{ fun lettersCount

  • Kotlin协程之Flow基础原理示例解析

    目录 引言 一.Flow的创建 二.Flow的消费 1.SafeFlow类 2.AbstractFlow类 3. SafeCollector类 4.消费过程中的挂起 引言 本文分析示例代码如下: launch(Dispatchers.Main) { flow { emit(1) emit(2) }.collect { delay(1000) withContext(Dispatchers.IO) { Log.d("liduo", "$it") } Log.d(&qu

  • Kotlin协程之Flow异常示例处理

    目录 示例 一.catch方法 catchImpl方法 二. onCompletion方法 1.unsafeFlow方法 2.ThrowingCollector类 三. retryWhen方法 示例 代码如下: launch(Dispatchers.Main) { // 第一部分 flow { emit(1) throw NullPointerException("e") }.catch { Log.d("liduo", "onCreate1: $it&q

  • Kotlin协程的基础与使用示例详解

    目录 一.协程概述 1.概念 2.特点 3.原理 1)续体传递 2)状态机 二.协程基础 1.协程的上下文 2.协程的作用域 3.协程调度器 4.协程的启动模式 5.协程的生命周期 1)协程状态的转换 2)状态标识的变化 三.协程使用 1.协程的启动 1)runBlocking方法 2)launch方法 3)async方法 4)suspend关键字 5)withContext方法 6)suspend方法 2.协程间通信 1)Channel 2)Channel的容量 3)produce方法与act

  • Kotlin协程Flow生命周期及异常处理浅析

    目录 正文 Flow基本概念 Flow生命周期 处理异常 上游或者中间异常使用catch 下游使用try-catch 切换执行线程 终止操作符 "冷的数据流"从何而来 正文 Kotlin协程中的Flow主要用于处理复杂的异步数据,以一种”流“的方式,从上到下依次处理,和RxJava的处理方式类型,但是比后者更加强大. Flow基本概念 Flow中基本上有三个概念,即 发送方,处理中间层,接收方,可以类比水利发电站中的上游,发电站,下游的概念, 数据从上游开始发送”流淌“至中间站被”处理

  • Kotlin协程Dispatchers原理示例详解

    目录 前置知识 demo startCoroutineCancellable intercepted()函数 DefaultScheduler中找dispatch函数 Runnable传入 Worker线程执行逻辑 小结 前置知识 Kotlin协程不是什么空中阁楼,Kotlin源代码会被编译成class字节码文件,最终会运行到虚拟机中.所以从本质上讲,Kotlin和Java是类似的,都是可以编译产生class的语言,但最终还是会受到虚拟机的限制,它们的代码最终会在虚拟机上的某个线程上被执行. 之

  • Kotlin协程launch原理详解

    目录 正文 launch使用 launch原理 CoroutineStart中找invoke方法 startCoroutineCancellable逻辑 小结 正文 launch我们经常用,今天来看看它是什么原理. 建议: 食用本篇文章之前记得先食用Kotlin协程之createCoroutine和startCoroutine launch使用 launch我们应该很熟悉了,随便举个例子: fun main() { val coroutineScope = CoroutineScope(Job(

  • Kotlin协程flowOn与线程切换超详细示例介绍

    目录 示例代码 一.flowOn方法 1.ChannelFlowOperatorImpl类 二.collect方法 1.ChannelFlowOperator类的collect方法 2.ChannelFlow类的collect方法 3.flow方法中代码的执行 4.接收flow方法发出的值 三.flowOn方法与流的融合 四.总结 示例代码 本文分析示例代码如下: launch(Dispatchers.Main) { flow { emit(1) emit(2) }.flowOn(Dispatc

  • Kotlin协程开发之Flow的融合与Channel容量及溢出策略介绍

    目录 一.协程间的通信 1.通道容量 2.溢出策略 二.FusibleFlow接口 三.ChannelFlow类 一.协程间的通信 当需要进行协程间的通信时,可以调用Channel方法,创建一个Channel接口指向的对象,通过调用该对象的send方法和receive方法实现消息的发送与接收.协程对Channel接口的实现,本质上与阻塞队列类似,这里不再赘述. 1.通道容量 事实上,send方法与receive方法并没有定义在Channel接口中,而是分别定义在SendChannel接口和Rec

  • Kotlin协程概念原理与使用万字梳理

    目录 一.协程概述 1.概念 2.特点 3.原理 二.协程基础 1.协程的上下文 2.协程的作用域 3.协程调度器 4.协程的启动模式 5.协程的生命周期 三.协程使用 1.协程的启动 2.协程间通信 3.多路复用 4.序列生成器 5.协程异步流 6.全局上下文 一.协程概述 1.概念 协程是Coroutine的中文简称,co表示协同.协作,routine表示程序.协程可以理解为多个互相协作的程序.协程是轻量级的线程,它的轻量体现在启动和切换,协程的启动不需要申请额外的堆栈空间:协程的切换发生在

  • 使用kotlin协程提高app性能(译)

    协程是一种并发设计模式,您可以在Android上使用它来简化异步执行的代码.Kotlin1.3版本添加了 Coroutines,并基于其他语言的既定概念. 在Android上,协程有助于解决两个主要问题: 管理长时间运行的任务,否则可能会阻止主线程并导致应用冻结. 提供主安全性,或从主线程安全地调用网络或磁盘操作. 本主题描述了如何使用Kotlin协程解决这些问题,使您能够编写更清晰,更简洁的应用程序代码. 管理长时间运行的任务 在Android上,每个应用程序都有一个主线程来处理用户界面并管理

随机推荐