Kotlin Flow 相关笔记

本文深入剖析了Kotlin Flow的实现原理,包括flow()方法的工作机制、线程切换逻辑、StateFlow与SharedFlow的区别及其应用场景,以及如何利用Flow.shareIn与Flow.stateIn提升性能。
摘要由CSDN通过智能技术生成

参考文章:https://developer.android.com/kotlin/flow?hl=zh-cn

涉及到的源码版本(不同版本实现可能有出入):
在这里插入图片描述


flow() 方法

以下内容,都是基于 flow() 顶级方法的内部实现是基于 SafeCollectorSafeFlow 来说明的。

实现原理

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

接收一个 FlowCollector<T>.() -> Unit 类型的 suspendable lambda 表达式,返回 SafeFlow,使用示例:

private val block: (suspend FlowCollector<Int>.() -> Unit) = {
    val random = Random(9)
    while (true) {
        // 调用 FlowCollector.emit() 发射数据
        emit(random.nextInt())
    }
}
private val testFlow: Flow<Int> = flow<Int>(block)

而返回的 SafeFlow 实现了 interface Flow<out T>。注意,并不是通过 Flow 来发射数据,而是由 lamdba 回调传入的 FlowCollector 实例来发射数据。

viewModelScope.launch {
    val collector = object : FlowCollector<Int> {
        override suspend fun emit(value: Int) {
            // do something
        }
    }
    testFlow.collect(collector)
}
private class SafeFlow<T>(private val block: suspend FlowCollector<T>.() -> Unit) : Flow<T> {
    override suspend fun collect(collector: FlowCollector<T>) {
        // 先创建 SafeCollector 实例,然后再执行 block lambda
        SafeCollector(collector, coroutineContext).block()
    }
}

对于 SafeFlow,会在 collect() 被调用时实例化出 SafeCollector 对象,并执行最开始传入的 FlowCollector<T>.() -> Unit 类型的 block lambda。即执行前面提到的这段逻辑,

val random = Random(9)
while (true) {
    // 调用 FlowCollector.emit() 发射数据
    // 伪代码
    this.SafeCollector.emit(random.nextInt())
}

因此在调用 this.SafeCollector.emit(random.nextInt()) 的时候,实际上是触发testFlow.collect(collector) 传入的 collector.emit() 回调方法:

internal class SafeCollector<T>(
    private val collector: FlowCollector<T>,
    private val collectContext: CoroutineContext
) : FlowCollector<T> {
 
    override suspend fun emit(value: T)  {
        ...
        collector.emit(value)
    }
    
    ...
    
}

所以简单的说,在 flow(){} 中通过 FlowCollector.emit() 发射数据,实际上会触发 Flow.collect() 时传入的 FlowCollector.emit() 回调方法,注意,两个 FlowCollector 不是直接的同一对象。
且是在调用 Flow.collect() 的时候才会触发 block lambda 的执行。

关于线程切换

viewModelScope.launch {
    val collector = object : FlowCollector<Int> {
        override suspend fun emit(value: Int) {
            // do something
            // 所处线程由外层的协程决定,即 viewModelScope.launch() 决定
        }
    }
    testFlow
        .map {}
        .flowOn(Dispatchers.IO) // 第一个 flowOn
        .map {}
        .flowOn(Dispatchers.Main)
        .collect(collector)
}
private val block: (suspend FlowCollector<Int>.() -> Unit) = {
    // 所处线程由 Flow<T>.flowOn() 决定
    val random = Random(9)
    while (true) {
        // 调用 FlowCollector.emit() 发射数据
        emit(random.nextInt())
    }
}
private val testFlow: Flow<Int> = flow<Int>(block)
  1. block suspendable lambda 所处的线程,是由链式表达式中的第一个 flowOn() 来决定的,
  2. collect(collector)collector.emit() 回调所处线程(即消费数据时所处的线程),是由当前协程指定的线程决定的。

补充说明

通过 flow<Int>(block) 得到的 SafeFlow 对象,是可以被多次 collect() 的,只不过需要注意,FlowCollector#emit() 是 suspendable 的,所以在同一协程作用域下的一段逻辑的执行进度,会受到 block lambda 内逻辑执行进度的影响。
比如有如下逻辑:

private val block: (suspend FlowCollector<Int>.() -> Unit) = {
    val random = Random(9)
    val nextInt = random.nextInt()
    this.emit(nextInt)
    // 定义一个死循环
    while (true) { }
}
viewModelScope.launch {
    Log.d("TAG", "${Thread.currentThread()}, start ++++++++++++++")
    val collector = object : FlowCollector<Int> {        
        override suspend fun emit(value: Int) {
            // do thing
        }
    }
    testFlow
        .flowOn(Dispatchers.IO)
        .collect(collector)
        
    Log.d("TAG", "${Thread.currentThread()}, end ++++++++++++++")
}

由于在 block lambda 中代码段末尾,定义了一个死循环,即这段逻辑一直不会结束,因而在 viewModelScope.launch {} 代码段中的最后一行(打印 end 日志的逻辑)也会一直得不到执行。

StateFlow 与 SharedFlow

StateFlow

https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/-state-flow/index.html

StateFlow 是一个状态容器式可观察数据流,可以向其收集器发出当前状态更新和新状态更新。还可通过其 value 属性读取当前状态值(与 SharedFlow 的主要区别)。如需更新状态并将其发送到数据流,请为 MutableStateFlow 类的 value 属性分配一个新值。

// 实际上是一个接口,定义了一个 value 字段表示当前状态值。
public interface StateFlow<out T> : SharedFlow<T> {
    /**
     * The current value of this state flow.
     */
    public val value: T
}

可以通过顶级方法 MutableStateFlow() 来获取一个 value 可写类型的 StateFlow

public fun <T> MutableStateFlow(value: T): MutableStateFlow<T> = StateFlowImpl(value ?: NULL)

其实现类型则是 StateFlowImpl,即 MutableStateFlow 类型。
需要注意的是,在 View 中订阅监听的时候,需要注意生命周期的问题,如下示例:

// 在 ViewModel 中定义
private val _uiState = MutableStateFlow<Int>(0)
val uiState: StateFlow<Int> = _uiState

// 在 Activity 中使用
// (1)
lifecycleScope.launchWhenStarted {
    viewModel.uiState.collect {
        // do something
    }
}

//(2)
lifecycleScope.launch {
    // repeatOnLifecycle launches the block in a new coroutine every time the
    // lifecycle is in the STARTED state (or above) and cancels it when it's STOPPED.
    repeatOnLifecycle(Lifecycle.State.STARTED) {
        // Trigger the flow and start listening for values.
        // Note that this happens when lifecycle is STARTED and stops
        // collecting when the lifecycle is STOPPED
        viewModel.uiState.collect {
            
        }
    }
}

因为不会像 LiveData 那样在监听的时候直接关联 LifecycleOwner,在使用的时候需要注意基于 LifecycleCoroutineScope#launchWhenXXX() 或者 LifecycleOwner#repeatOnLifecycle() 来保证与 flow 相关的逻辑生命周期可控。

注意:repeatOnLifecycle API 仅在androidx.lifecycle:lifecycle-runtime-ktx:2.4.0-alpha01 库及更高版本中提供。

与 LiveData 比较

用法类似,用途也基本上都是做可观察的数据容器类。但是也有些区别:

  • LiveData 在订阅监听的时候,会基于 LifecycleOwner 来保证逻辑生命周期可控,而 StateFlow 需要借助如上的方法;
  • StateFlow 在初始化的时候必须赋予初始值。

SharedFlow

https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/-shared-flow/

可以通过 MutableSharedFlow() 方法获取可变类型的 SharedFlow 实例。

public fun <T> MutableSharedFlow(
    replay: Int = 0,
    extraBufferCapacity: Int = 0,
    onBufferOverflow: BufferOverflow = BufferOverflow.SUSPEND
): MutableSharedFlow<T> {}

Creates a MutableSharedFlow with the given configuration parameters.
This function throws IllegalArgumentException on unsupported values of parameters or combinations thereof.
Params:
replay - the number of values replayed to new subscribers (cannot be negative, defaults to zero).
extraBufferCapacity - the number of values buffered in addition to replay. emit does not suspend while there is a buffer space remaining (optional, cannot be negative, defaults to zero).
onBufferOverflow - configures an action on buffer overflow (optional, defaults to suspending attempts to emit a value, supported only when replay > 0 or extraBufferCapacity > 0).

  • replay,为新的订阅者发送包含最新的指定 replay 个数的值,只能是非负数,默认 0;
  • extraBufferCapacity,除了 replay 数量之外的缓冲容量,在达到容量之前,emit() 不会挂起,默认为 0;
  • onBufferOverflow,缓冲溢出时的可选操作(只有在 replay>0 or extraBufferCapacity>0 时有效),默认挂起 emit(),可选值有 SUSPENDDROP_LATESTDROP_OLDEST

另外,还包含 subscriptionCount 属性用于获取有效的 collecter 数量:

/**
 * The number of subscribers (active collectors) to this shared flow.
 *
 * The integer in the resulting [StateFlow] is not negative and starts with zero for a freshly created
 * shared flow.
 *
 * This state can be used to react to changes in the number of subscriptions to this shared flow.
 * For example, if you need to call `onActive` when the first subscriber appears and `onInactive`
 * when the last one disappears, you can set it up like this:
 *
 * ```
 * sharedFlow.subscriptionCount
 *     .map { count -> count > 0 } // map count into active/inactive flag
 *     .distinctUntilChanged() // only react to true<->false changes
 *     .onEach { isActive -> // configure an action
 *         if (isActive) onActive() else onInactive()
 *     }
 *     .launchIn(scope) // launch it
 * ```
 */
public val subscriptionCount: StateFlow<Int>

相比于 StateFlow 的使用场景:

  • 发生订阅时,需要将过去已经更新的 n 个值,同步给新的订阅者;
  • 配置缓存策略。

冷/热数据流

热数据流(hot stream),没有消费者的时候也可以生产数据,如 StateFlowSharedFlow
冷数据流(cold stream),需要在 collect() 之后才生产值,即无消费者的时候不会生产数据,比如前面的 flow() 方法涉及到的使用。

Note: Cold flows are created on-demand and emit data when they’re being observed. Hot flows are always active and can emit data regardless of whether or not they’re being observed.
from https://medium.com/androiddevelopers/things-to-know-about-flows-sharein-and-statein-operators-20e6ccb2bc74

Flow.shareIn 与 Flow.stateIn

参考文章:https://medium.com/androiddevelopers/things-to-know-about-flows-sharein-and-statein-operators-20e6ccb2bc74

用途

两者都是用于将 cold stream 转换为 hot stream,shareIn 返回 SharedFlowstateIn 返回 StateFlow

目的

  • 提高性能:
    • cold stream 是每次在被 observe(即被 collect)的时候,才会生成生产内容,这表示每次被 observe 的时候,每一个 collector 获取到的都是新的数据实例,同时产生新的中间对象(注意不是说产生不同的 Flow 对象,就像前面提到的 flow() 定级方法,得到的是 cold stream 类型的 Flow 对象,而该 Flow 对象每次被 collect 的时候,都会对应生产新的 FlowController 实例,即中间对象,用于针对每个不同的 collector 来生产内容);
    • 而 hot stream,每次被 observe (即被 collect)的时候,所有的 collector 都是共享的相同的数据实例。
  • 实现 Buffering Events,通过 shareIn 来转换为 SharedFlow,从而通过其replay 参数,来缓冲指定个数的之前被 emit 的内容,使得新的 collector 在 obserbe 的时候总是能获取到之前被缓冲的内容;
  • 实现 Caching Data,通过 stateIn 来转换为 StateFlow,实现为新的 collector 缓存此前最后一次被 emit 的内容。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值