[Android][kotlin]Flow数据流

Flow 是一个异步数据流,它可以顺序地发出数据,通过流上的一些中间操作得出结果;若出错可抛出异常。这些 “流上的中间操作” 包括但不限于 mapfiltertakezip 等等方法。这些中间操作是链式的,可以在后面再次添加其他操作方法,并且也不是挂起函数,它们只是构建了一条链式的操作并实时返回结果给后面的操作步骤。

组成

Flow 一般包含三个部分:
1)提供方:负责生成数据并添加到 Flow 中,得益于协程,Flow 可以异步生成数据;
2)中介(可选):可对 Flow 中的值进行操作、修改;也可修改 Flow 本身的一些属性,如所在线程等;
3)使用方:接收并使用 Flow 中的值。
提供方:生产者,使用方:消费者,典型的生产者消费者模式。

冷流与热流

  • 冷流(Cold Flow):在数据被使用方订阅后,即调用 collect 方法之后,提供方才开始执行发送数据流的代码,通常是调用 emit 方法。即不消费,不生产,多次消费才会多次生产。使用方和提供方是一对一的关系。
  • 热流(Hot Flow):无论有无使用方,提供方都可以执行发送数据流的操作,提供方和使用方是一对多的关系。热流就是不管有无消费,都可生产。

SharedFlow 就是热流的一种,任何流也可以通过 stateIn 和 shareIn 操作转化为热流,或者通过 produceIn 操作将流转化为一个热通道也能达到目的。

常用的操作符

Flow 的使用依赖于众多的操作符,这些操作符可以大致地分为 中间操作符 与 末端操作符 两大类。中间操作符是流上的中间操作,可以针对流上的数据做一些修改,是链式调用。中间操作符与末端操作符的区别是:中间操作符是用来执行一些操作,不会立即执行,返回值还是个 Flow;末端操作符就会触发流的执行,返回值不是 Flow。

一个完整的 Flow 是由 Flow 构建器Flow 中间操作符Flow 末端操作符 组成,如下示意图所示:

构造器

flow{...}

这个方法可以在其内部顺序调用 emit 方法或 emitAll 方法从而构造一个顺序执行的 Flow。emit 是发射单个值;emitAll 是发射一个流,这两个方法分别类似于 list.add(item)list.addAll(list2)方法。flow {···} 方法的源码如下:

public fun <T> flow(@BuilderInference block: suspend FlowCollector<T>.() -> Unit): Flow<T> = SafeFlow(block)

需要额外注意的是,flow 后面的 lambda 表达式是一个挂起函数,里面不能使用不同的 CoroutineContext 来调用 emit 方法去发射值。因此,在 flow{...} 中不要通过创建新协程或使用 withContext 代码块在另外的 CoroutineContext 中调用 emit 方法,否则会报错。如果确实有这种需求,可以使用 channelFlow 操作符。

val testFlow = flow {
    emit(23)
//    withContext(Dispatchers.Main) { // error
//        emit(24)
//    }
    delay(3000)
    emitAll(flowOf(25,26))
}
asFlow()

asFlow() 操作符。是集合的扩展方法,可将其他数据转换成 Flow,例如 Array 的扩展方法:

public fun <T> Array<T>.asFlow(): Flow<T> = flow {
    forEach { value ->
        emit(value)
    }
}

不仅 Array 扩展了此方法,各种其他数据类型的数组都扩展了此方法。所以集合可以很方便地构造一个 Flow。

flowOf()

flowOf() 方法。用于快速创建流,类似于 listOf() 方法,下面是它的源码:

public fun <T> flowOf(vararg elements: T): Flow<T> = flow {
    for (element in elements) {
        emit(element)
    }
}

注意到 Flow 初始化的时候跟其他对象一样,作用域在哪儿都可以,但 collect 收集的时候就需要放在协程里了,因为 collect 是个挂起函数:

val testFlow = flowOf(65,66,67)
lifecycleScope.launch {
    testFlow.collect {
        println("输出:$it")
    }
}
//打印结果:
//输出:65
//输出:66
//输出:67
channelFlow{...}

channelFlow {···} 方法。这个方法就可以在内部使用不同的 CoroutineContext 来调用 send 方法去发射值,而且这种构造方法保证了线程安全也保证了上下文的一致性,源码如下:

public fun <T> channelFlow(@BuilderInference block: suspend ProducerScope<T>.() -> Unit): Flow<T> =
    ChannelFlowBuilder(block)

一个简单的使用例子:

val testFlow1 = channelFlow {
    send(20)
    withContext(Dispatchers.IO) { //可切换线程
        send(22)
    }
}
lifecycleScope.launch {
    testFlow1.collect {
        println("输出 = $it")
    }
}
MutableStateFlow

MutableStateFlow 操作符,可以定义相应的构造函数去创建一个可以直接更新的热流,后面在StateFlow和ShareFlow会详细说明。

MutableSharedFlow

MutableSharedFlow 操作符,可以定义相应的构造函数去创建一个可以直接更新的热流,后面在StateFlow和ShareFlow会详细说明。

中间操作符

zip

顾名思义,zip中间操作符就是可以将两个 Flow 汇合成一个 Flow:

lateinit var testFlow1: Flow<String>
lateinit var testFlow2: Flow<String>
private fun setupTwoFlow() {
    testFlow1 = flowOf("Red", "Blue", "Green")
    testFlow2 = flowOf("fish", "sky", "tree", "ball")
    CoroutineScope(Dispatchers.IO).launch {
        testFlow1.zip(testFlow2) { firstWord, secondWord ->
            "$firstWord $secondWord"
        }.collect {
            println("+++ $it +++")
        }
    }
}

// 输出结果:
//com.example.myapplication I/System.out: +++ Red fish +++
//com.example.myapplication I/System.out: +++ Blue sky +++
//com.example.myapplication I/System.out: +++ Green tree +++

//zip 方法声明:
public fun <T1, T2, R> Flow<T1>.zip(other: Flow<T2>, transform: suspend (T1, T2) -> R): Flow<R> = zipImpl(this, other, transform)

从 zip 方法的声明中可知,zip 方法的第二个参数就是针对两个 Flow 进行各种处理的挂起函数,也可如例子中写成尾调函数的样子,返回值是处理之后的 Flow。而且当两个 Flow 长度不一样时,最后的结果会默认剔除掉先前较长的 Flow 中的元素。所以 testFlow2 中的 “ball” 就被自动剔除掉了。

filter

用来将Flow中的数据进行筛选处理,然后交给下一个操作符

map

用来将 Flow 中的数据一个个拿出来做各自的处理,然后交给下一个操作符。

check

类似于一个检查站,满足括号内条件的数据可以通过,不满足则交给它的尾调函数处理,并且抛出异常。

onCompletion

Flow 最后的兜底器。无论 Flow 最后是执行完成、被取消、抛出异常,都会走到 onCompletion 操作符中,类似于在 Flow 的 collect 函数外加了个 tryfinally

catch

不用多说,专门用于捕捉异常的,避免程序崩溃。这里如果把 catch 去掉,程序就会崩溃。如果把 catch和 onCompletion 操作符位置调换,则 onCompletion 里面就接收不到异常信息了。

末端操作符

collect

collect 操作符是个挂起函数,需要在协程作用域中调用;并且它是一个末端操作符,末端操作符就是实际启动 Flow 执行的操作符,这一点跟 RxJava 中的 Observable 对象的执行很像。Flow 在调用 collect 操作符收集流之前,Flow 构建器和中间操作符都不会执行

val testFlow2 = flow {
    println("++++ 开始")
    emit(40)
    println("++++ 发出了40")
    emit(50)
    println("++++ 发出了50")
}
lifecycleScope.launch {
    testFlow2.collect{
        println("++++ 收集 = $it")
    }
}
// 输出结果:
//com.example.myapplication I/System.out: ++++ 开始
//com.example.myapplication I/System.out: ++++ 收集 = 40
//com.example.myapplication I/System.out: ++++ 发出了40
//com.example.myapplication I/System.out: ++++ 收集 = 50
//com.example.myapplication I/System.out: ++++ 发出了50

每次到 collect 方法调用时,才会去执行 emit 方法,而在此之前,emit 方法是不会被调用的。这种 Flow 就是冷流。

reduce

reduce 也是一个末端操作符,它的作用就是将 Flow 中的数据两两组合接连进行处理,跟 Kotlin 集合中的 reduce 操作符作用相同。

private fun reduceOperator() {
    val testFlow = listOf("w","i","f","i").asFlow()
    CoroutineScope(Dispatchers.Default).launch {
        val result = testFlow.reduce { accumulator, value ->
            println("+++accumulator = $accumulator  value = $value")
            "$accumulator$value"
        }
        println("+++final result = $result")
    }
}
//输出结果:
//com.example.myapplication I/System.out: +++accumulator = w  value = i
//com.example.myapplication I/System.out: +++accumulator = wi  value = f
//com.example.myapplication I/System.out: +++accumulator = wif  value = i
//com.example.myapplication I/System.out: +++final result = wifi

两个值处理后得到的新值作为下一轮中的输入值之一,这就是两两接连进行处理的意思。

toList

toList 操作符也是一种末端操作符,可以将 Flow 返回的多个值放进一个 List 中返回,返回的 List 也可以自己设置。

stateIn

stateIn 操作符也是一种末端操作符,可以将Flow从冷流转为热流,后面在StateFlow和ShareFlow会详细说明。

shareIn

shareIn 操作符也是一个末端操作符,可以将Flow从冷流转为热流,后面在StateFlow和ShareFlow会详细说明。

  • 11
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Android开发中,Kotlin是一种流行的现代化编程语言,常用于构建Android应用。对于消息传递,Kotlin提供了多种选项,其中常见的包括: 1. **BroadcastReceiver**: Android中的BroadcastReceiver是一个经典的消息传递机制,允许应用程序接收和处理系统级别的广播事件,如设备状态改变或新的网络连接等。 2. **EventBus**: EventBus是一个轻量级库,支持发布/订阅模式,使得不同组件间可以轻松地发送和接收事件,不需要依赖于直接的继承或接口。 3. **LiveData & ViewModel**: 这是Android Jetpack Architecture Components的一部分,LiveData允许数据持有者更新并通知观察者,而ViewModel则作为数据和UI之间的桥梁,通过LiveData传递消息给UI组件。 4. **Kotlin Coroutines**: Kotlin的协程(Coroutine)提供了异步编程的能力,通过`launch`, `async`等函数可以在主线程或子线程之间传递消息,执行任务并处理回调。 5. **Retrofit + RxJava**: 如果涉及到网络请求,Retrofit(RESTful API客户端)配合RxJava(响应式编程框架)可以方便地处理HTTP请求和错误,并提供流式的数据处理。 6. **Kotson (or Gson)**: 数据序列化库,用于将JSON格式的消息转换为Kotlin对象,反之亦然。 7. **Channel API**: 高性能的事件传递工具,适用于即时通信场景,比如Jetpack Compose中的StateFlow就是基于通道API实现的。 相关问题: 1. 在Android中,Kotlin如何利用LiveData进行消息传递? 2. 使用Retrofit配合RxJava时,如何处理从服务器返回的消息? 3. Kotson在处理JSON消息传递时有哪些优势?
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值