乐趣区

关于android:Flow-操作符-shareIn-和-stateIn-使用须知

Flow.shareIn 与 Flow.stateIn 操作符能够将冷流转换为热流: 它们能够将来自上游冷数据流的信息播送给多个收集者。这两个操作符通常用于晋升性能: 在没有收集者时退出缓冲;或者罗唆作为一种缓存机制应用。

留神 : 冷流 是按需创立的,并且会在它们被察看时发送数据; 热流 则总是沉闷,无论是否被察看,它们都能发送数据。

本文将会通过示例帮您相熟 shareIn 与 stateIn 操作符。您将学到如何针对特定用例配置它们,并防止可能遇到的常见陷阱。

底层数据流生产者

持续应用我 之前文章 中应用过的例子——应用底层数据流生产者收回地位更新。它是一个应用 callbackFlow 实现的 冷流。每个新的收集者都会触发数据流的生产者代码块,同时也会将新的回调退出到 FusedLocationProviderClient。

class LocationDataSource(private val locationClient: FusedLocationProviderClient) {
    val locationsSource: Flow<Location> = callbackFlow<Location> {val callback = object : LocationCallback() {override fun onLocationResult(result: LocationResult?) {
                result ?: return
                try {offer(result.lastLocation) } catch(e: Exception) {}}
        }
        requestLocationUpdates(createLocationRequest(), callback, Looper.getMainLooper())
            .addOnFailureListener { e ->
                close(e) // in case of exception, close the Flow
            }
        // 在 Flow 完结收集时进行清理
        awaitClose {removeLocationUpdates(callback)
        }
    }
}

让咱们看看在不同的用例下如何应用 shareIn 与 stateIn 优化 locationsSource 数据流。

shareIn 还是 stateIn?

咱们要探讨的第一个话题是 shareInstateIn 之间的区别。shareIn 操作符返回的是 SharedFlow 而 stateIn 返回的是 StateFlow。

留神 : 要理解无关 StateFlowSharedFlow 的更多信息,能够查看 咱们的文档。

StateFlow 是 SharedFlow 的一种非凡配置,旨在优化分享状态: 最初被发送的我的项目会从新发送给新的收集者,并且这些我的项目会应用 Any.equals 进行合并。您能够在 StateFlow 文档 中查看更多相干信息。

两者之间的最次要区别,在于 StateFlow 接口容许您通过读取 value 属性同步拜访其最初收回的值。而这不是 SharedFlow 的应用形式。

晋升性能

通过共享所有收集者要察看的同一数据流实例 (而不是按需创立同一个数据流的新实例),这些 API 能够为咱们晋升性能。

在上面的例子中,LocationRepository 生产了 LocationDataSource 裸露的 locationsSource 数据流,同时应用了 shareIn 操作符,从而让每个对用户地位信息感兴趣的收集者都从同一数据流实例中收集数据。这里只创立了一个 locationsSource 数据流实例并由所有收集者共享:

class LocationRepository(
    private val locationDataSource: LocationDataSource,
    private val externalScope: CoroutineScope
) {
    val locations: Flow<Location> = 
        locationDataSource.locationsSource.shareIn(externalScope, WhileSubscribed())
}

WhileSubscribed 共享策略用于在没有收集者时勾销上游数据流。这样一来,咱们便能在没有程序对地位更新感兴趣时防止资源的节约。

Android 利用小揭示! 在大部分状况下,您能够应用 WhileSubscribed(5000),当最初一个收集者隐没后再放弃上游数据流沉闷状态 5 秒钟。这样在某些特定状况 (如配置扭转) 下能够防止重启上游数据流。当上游数据流的创立老本很高,或者在 ViewModel 中应用这些操作符时,这一技巧尤其有用。

缓冲事件

在上面的例子中,咱们的需要有所扭转。当初要求咱们 放弃 监听地位更新,同时要在利用从后盾返回前台时在屏幕上显示最初的 10 个地位:

class LocationRepository(
    private val locationDataSource: LocationDataSource,
    private val externalScope: CoroutineScope
) {
    val locations: Flow<Location> = 
        locationDataSource.locationsSource
            .shareIn(externalScope, SharingStarted.Eagerly, replay = 10)
}

咱们将参数 replay 的值设置为 10,来让最初收回的 10 个我的项目放弃在内存中,同时在每次有收集者察看数据流时从新发送这些我的项目。为了放弃外部数据流始终处于沉闷状态并发送地位更新,咱们应用了共享策略 SharingStarted.Eagerly,这样就算没有收集者,也能始终监听更新。

缓存数据

咱们的需要再次发生变化,这次咱们不再须要利用处于后盾时 继续 监听地位更新。不过,咱们须要缓存最初发送的我的项目,让用户在获取以后地位时能在屏幕上看到一些数据 (即便数据是旧的)。针对这种状况,咱们能够应用 stateIn 操作符。

class LocationRepository(
    private val locationDataSource: LocationDataSource,
    private val externalScope: CoroutineScope
) {
    val locations: Flow<Location> = 
        locationDataSource.locationsSource.stateIn(externalScope, WhileSubscribed(), EmptyLocation)
}

Flow.stateIn 能够缓存最初发送的我的项目,并重放给新的收集者。

留神!不要在每个函数调用时创立新的实例

切勿 在调用某个函数调用返回时,应用 shareIn 或 stateIn 创立新的数据流。这样会在每次函数调用时创立一个新的 SharedFlow 或 StateFlow,而它们将会始终放弃在内存中,直到作用域被勾销或者在没有任何援用时被垃圾回收。

class UserRepository(
    private val userLocalDataSource: UserLocalDataSource,
    private val externalScope: CoroutineScope
) {
    // 不要像这样在函数中应用 shareIn 或 stateIn 
    // 这将在每次调用时创立新的 SharedFlow 或 StateFlow,而它们将不会被复用。fun getUser(): Flow<User> =
        userLocalDataSource.getUser()
            .shareIn(externalScope, WhileSubscribed())    

    // 能够在属性中应用 shareIn 或 stateIn 
    val user: Flow<User> = 
        userLocalDataSource.getUser().shareIn(externalScope, WhileSubscribed())
}

须要入参的数据流

须要入参 (如 userId) 的数据流无奈简略地应用 shareInstateIn 共享。以开源我的项目——Google I/O 的 Android 利用 iosched 为例,您能够在 源码中 看到,从 Firestore 获取用户事件的数据流是通过 callbackFlow 实现的。因为其接管 userId 作为参数,因而无奈简略应用 shareInstateIn 操作符对其进行复用。

class UserRepository(private val userEventsDataSource: FirestoreUserEventDataSource) {
    // 新的收集者会在 Firestore 中注册为新的回调。// 因为这一函数依赖一个 `userId`,所以在这个函数中
    // 数据流无奈通过调用 shareIn 或 stateIn 进行复用.
    // 这样会导致每次调用函数时,都会创立新的  SharedFlow 或 StateFlow
    fun getUserEvents(userId: String): Flow<UserEventsResult> =
        userLocalDataSource.getObservableUserEvents(userId)
}

如何优化这一用例取决于您利用的需要:

  • 您是否容许同时从多个用户接管事件?如果答案是必定的,您可能须要为 SharedFlowStateFlow 实例创立一个 map,并在 subscriptionCount 为 0 时移除援用并退出上游数据流。
  • 如果您只容许一个用户,并且收集者须要更新为察看新的用户,您能够向一个所有收集者共用的 SharedFlowStateFlow 发送事件更新,并将公共数据流作为类中的变量。

shareInstateIn 操作符能够与冷流一起应用来晋升性能,您能够应用它们在没有收集者时增加缓冲,或者间接将其作为缓存机制应用。小心应用它们,不要在每次函数调用时都创立新的数据流实例——这样会导致资源的节约及意料之外的问题!

退出移动版