Kotlin Flow

本文围绕 Kotlin 的 Flow 展开,介绍其适合实时数据更新和无休止数据流,已集成到许多 Jetpack 库中。阐述了创建和订阅 Flow 数据流的方法,对比了冷、热数据流,还详细讲解了 StateFlow、SharedFlow 的特性及使用,如 StateFlow 配合协程可完成复杂线程切换等。

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

Flow 非常适合实时数据更新和无休止的数据流,Flow 已经被集成到许多 Jetpack 库中

@Dao
abstract class ExampleDao {
    @Query("SELECT * FROM Example")
    abstract fun getExamples(): Flow<List<Example>>
}

创建 Flow 数据流

class NewsRemoteDataSource(
    private val newsApi: NewsApi,
    private val refreshIntervalMs: Long = 5000
) {
    val latestNews: Flow<List<ArticleHeadline>> = flow {
        while (true) {
            val latestNews = newsApi.fetchLatestNews()
            emit(latestNews) // Emits the result of the request to the flow
            delay(refreshIntervalMs) // Suspends the coroutine for some time
        }
    }.map { it }
        .flowOn(Dispatchers.IO)
        .catch { exception -> notifyError(exception) }
}

interface NewsApi {
    suspend fun fetchLatestNews(): List<ArticleHeadline>
}

订阅 Flow 数据流

class LatestNewsViewModel(
    private val newsRepository: NewsRepository
) : ViewModel() {
    init {
        viewModelScope.launch {
            // Trigger the flow and consume its elements using collect
            newsRepository.favoriteLatestNews.collect { favoriteNews ->
                // Update View with the latest favorite news
            }
        }
    }
}

在 默认的 Flow Builder 中,我们通过 emit 发射数据,而 callbackFlow / channelFlow 允许我们从不同的 CoroutineContext 中或者在协程之外通过 trySend 发射数据。callbackFlow / channelFlow 内部通过 Channel 实现,默认的缓冲区大小为64,如果超过该大小,则 trySend 返回 false

val latestNews: Flow<List<Int>> = channelFlow {
    newsApi.fetchLatestNews(object : NewsApiCallback {
        override fun onSuccess(list: List<Int>) {
            if (trySend(listOf(i)).isSuccess) {
                Log.e("scrutiny", "source trySend success")
            } else {
                Log.e("scrutiny", "source trySend failed")
            }
        }
        override fun onError() {}
    })
    delay(refreshIntervalMs)
    withContext(Dispatchers.IO) {
        trySend(listOf<Int>())
    }
    awaitClose {
        Log.e("scrutiny", "channelFlow close")
    }
}

冷数据流 VS 热数据流

相比于热数据流(StateFlow、SharedFlow),通过 flow / callbackFlow / channelFlow 创建的数据流是冷数据流,Consumer 每次 collect 都会重新执行发射逻辑

StateFlow

持有状态的 Flow,它向 Consumer 发出当前和新状态更新。 当前状态值也可以通过其 value 属性读取。 要更新状态并将其发送到流,请为 MutableStateFlow 类的 value 属性分配一个新值

通过 repeatOnlifecycle 可以用来替换 LiveData,用于解决复杂场景的线程切换问题

override fun onCreate(savedInstanceState: Bundle?) {
    // Start a coroutine in the lifecycle scope
    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
            latestNewsViewModel.uiState.collect { uiState ->
                // New value received
                when (uiState) {
                    is LatestNewsUiState.Success -> showFavoriteNews(uiState.news)
                    is LatestNewsUiState.Error -> showError(uiState.exception)
                }
            }
        }
    }
}

To convert any flow to a StateFlow, use the stateIn intermediate operator.

StateFlow vs LiveData

StateFlow and LiveData have similarities. Both are observable data holder classes, and both follow a similar pattern when used in your app architecture. Note, however, that StateFlow and LiveData do behave differently:

  • StateFlow requires an initial state to be passed in to the constructor, while LiveData does not.
  • LiveData.observe automatically unregisters the consumer when the view goes to the STOPPED state, whereas collecting from a StateFlow or any other flow does not stop collecting automatically. To achieve the same behavior,you need to collect the flow from a Lifecycle.repeatOnLifecycle block.
  • StateFlow 配合协程可以完成复杂场景的线程切换

 SharedFlow

通过 shareIn 函数可以将冷流 Flow 转换为热流 SharedFlow, SharedFlow 是 StateFlow 的高度可配置的泛化,具体参数有:

  • A CoroutineScope that is used to share the flow. This scope should live longer than any consumer to keep the shared flow alive as long as needed.
  • The number of items to replay to each new collector.
  • The start behavior policy.
/**
 * Creates a [MutableSharedFlow] with the given configuration parameters.
 *
 * @param replay the number of values replayed to new subscribers (defaults to zero).
 * @param extraBufferCapacity the number of values buffered in addition to `replay`.
 *   [emit][MutableSharedFlow.emit] does not suspend while there is a buffer
 *   space remaining (optional, cannot be negative, defaults to zero).
 * @param onBufferOverflow configures an [emit][MutableSharedFlow.emit] action
 * on buffer overflow. Optional, defaults to
 *   [suspending][BufferOverflow.SUSPEND] attempts to emit a value.
 *   Values other than [BufferOverflow.SUSPEND] are supported only when 
 *   `replay > 0` or `extraBufferCapacity > 0`.
 *   **Buffer overflow can happen only when there is at least one subscriber that
 *   is not ready to accept the new value.** In the absence of subscribers only
 *   the most recent [replay] values are stored and the buffer overflow behavior 
 *   is never triggered and has no effect.
 */
@Suppress("FunctionName", "UNCHECKED_CAST")
public fun <T> MutableSharedFlow(
    replay: Int = 0,
    extraBufferCapacity: Int = 0,
    onBufferOverflow: BufferOverflow = BufferOverflow.SUSPEND
): MutableSharedFlow<T> {

}

我们也可以通过 shareIn 操作符将冷数据流转换为 SharedFlow

val latestNews: Flow<List<ArticleHeadline>> = flow {
    ...
}.shareIn(
    externalScope,
    replay = 1,
    started = SharingStarted.WhileSubscribed()
)

SharingStarted 参数

public companion object {
    /**
     * Sharing is started immediately and never stops.
     */
    public val Eagerly: SharingStarted = StartedEagerly()

    /**
     * Sharing is started when the first subscriber appears and never stops.
     */
    public val Lazily: SharingStarted = StartedLazily()

    /**
     * Sharing is started when the first subscriber appears, immediately stops when the last
     * subscriber disappears (by default), keeping the replay cache forever (by default).
     *
     * It has the following optional parameters:
     *
     * * [stopTimeoutMillis] configures a delay (in milliseconds) between the disappearance of the last
     *   subscriber and the stopping of the sharing coroutine. It defaults to zero (stop immediately).
     * * [replayExpirationMillis] configures a delay (in milliseconds) between the stopping of
     *   the sharing coroutine and the resetting of the replay cache
     *   (which makes the cache empty for the [shareIn] operator and resets the cached value
     *   to the original `initialValue` for the [stateIn] operator).
     *   It defaults to `Long.MAX_VALUE` (keep replay cache forever, never reset buffer).
     *   Use zero value to expire the cache immediately.
     */
    @Suppress("FunctionName")
    public fun WhileSubscribed(
        stopTimeoutMillis: Long = 0,
        replayExpirationMillis: Long = Long.MAX_VALUE
    ): SharingStarted = StartedWhileSubscribed(stopTimeoutMillis, replayExpirationMillis)
}

现有 Task1、Task2 等多个并行任务,如何等待全部任务执行完成后,开始执行 Task3 ?

fun test_RxJava() {
    Observable.zip(
        Observable.fromCallable(Callable(task1)).subscribeOn(Schedulers.newThread()),
        Observable.fromCallable(Callable(task2)).subscribeOn(Schedulers.newThread()),
        BiFunction(task3)
    ).test().awaitTerminalEvent()
}

fun test_coroutine() {
    runBlocking {
        val c1 = async(Dispatchers.IO) {
            task1()
        }
        val c2 = async(Dispatchers.IO) {
            task2()
        }
        task3(c1.await(), c2.await())
    }
}

fun test_flow() {
    val flow1 = flow<String> { emit(task1()) }
    val flow2 = flow<String> { emit(task2()) }
    runBlocking {
        flow1.zip(flow2) { t1, t2 ->
             task3(t1, t2)
        }
        .flowOn(Dispatchers.IO)
        .collect()
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

little-sparrow

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值