[译]关于 Flow 的 shareIn 和 stateIn 操作符的注意事项

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

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
            }
        // clean up when Flow collection ends
        awaitClose {
            removeLocationUpdates(callback)
        }
    }
}

让我们看看如何使用 shareIn 和 stateIn 运算符来优化不同用例的 locationsSource 流。

shareIn 还是 stateIn ?

我们将讨论的第一个主题是 shareIn 和 stateIn 之间的区别。 shareIn 运算符返回一个 SharedFlow 实例,而 stateIn 返回一个 StateFlow。

注意:要了解有关 StateFlow 和 SharedFlow 的更多信息,请查看我们的文档

StateFlow 是 SharedFlow 的一种特殊配置,针对共享状态进行了优化:最后发出的项目被重放给新的收集器,元素使用 Any.equals 进行 conflate 合并。 您可以在 StateFlow 文档中阅读有关此内容的更多信息。

这些 API 之间的主要区别在于 StateFlow 接口允许您通过读取其 value 属性来同步访问最后发出的值。 SharedFlow 并非如此。

提升性能

这些 API 可以通过共享所有收集器要观察的流的相同实例而不是按需创建相同流的新实例来提高性能。

在下面的示例中,LocationRepository 使用 LocationDataSource 暴露的 locationsSource 流并应用 shareIn 运算符使对用户位置感兴趣的每个收集器都从流的同一实例中收集。 只为所有收集器创建和共享 locationSource 流的一个实例:

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)
} 

我们使用值为 10 的 replay 将最后 10 个发出的元素保留在内存中,并在每次收集器观察流时重新发出这些元素。 为了保持底层流始终处于活跃状态并发出位置更新数据,即使没有收集器,也可以使用 SharingStarted.Eagerly 策略来监听更新。
转载请说明出处:https://blog.csdn.net/hegan2010/article/details/121338700

缓冲数据

我们的要求再次发生了变化,在这种情况下,如果应用程序在后台,我们不需要总是监听位置更新。 但是,我们需要缓存最后发出的项目,以便用户在获取当前位置时始终可以在屏幕上看到一些数据,即使是陈旧的数据。 对于这种情况,我们可以使用 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
) {
    // DO NOT USE shareIn or stateIn in a function like this.
    // It creates a new SharedFlow/StateFlow per invocation which is not reused!
    fun getUser(): Flow<User> =
        userLocalDataSource.getUser()
            .shareIn(externalScope, WhileSubscribed())    

    // DO USE shareIn or stateIn in a property
    val user: Flow<User> = 
        userLocalDataSource.getUser().shareIn(externalScope, WhileSubscribed())
}

需要输入的流

需要输入的流(如 userId)无法使用 shareIn 或 stateIn 轻松共享。 以 iosched 开源项目(Google I/O 的 Android 应用程序)为例,从 Firestore 获取用户事件的流程是使用 callbackFlow 实现的,如您在源代码中所见。 由于它将 userId 作为参数,因此无法使用 shareIn 或 stateIn 运算符轻松重用此流。

class UserRepository(
    private val userEventsDataSource: FirestoreUserEventDataSource
) {
    // New collectors will register as a new callback in Firestore.
    // As this function depends on a `userId`, the flow cannot be
    // reused by calling shareIn or stateIn in this function.
    // That will cause a new Shared/StateFlow to be created
    // every time the function is called.
    fun getUserEvents(userId: String): Flow<UserEventsResult> =
        userLocalDataSource.getObservableUserEvents(userId)
}

优化此用例取决于您的应用程序的要求:

  • 您是否允许同时接收来自多个用户的事件? 您可能需要创建 SharedFlow/StateFlow 实例的映射,并在 subscriptionCount 达到零时删除引用并取消上游流。
  • 如果您只允许一个用户,并且所有收集器都需要更新为新用户,您可以向所有收集器的公共 SharedFlow/StateFlow 发出事件更新,并将公共流用作类中的变量。

. . .

shareIn 和 stateIn 运算符可以与冷流一起使用以提高性能,在收集器不存在时添加缓冲区,甚至作为缓存机制! 明智地使用它们,不要在每次函数调用时都创建新实例——它不会像你期望的那样工作!

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值