官方推荐 Flow 取代 LiveData,有必要吗?

本文详细介绍了Kotlin中的StateFlow和SharedFlow在AndroidMVVM架构中的使用,比较了它们的特点,以及如何通过WhileSubscribed策略优化性能和资源管理,还提到了LiveData的比较。

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

简单使用如下:

//ViewModel

val sharedFlow=MutableSharedFlow()

viewModelScope.launch{

sharedFlow.emit(“Hello”)

sharedFlow.emit(“SharedFlow”)

}

//Activity

lifecycleScope.launch{

viewMode.sharedFlow.collect {

print(it)

}

}

3.3 将冷流转化为SharedFlow

普通flow可使用shareIn扩展方法,转化成SharedFlow

val sharedFlow by lazy {

flow {

//…

}.shareIn(viewModelScope, WhileSubscribed(500), 0)

}

shareIn主要也有三个参数:

@param scope 共享开始时所在的协程作用域范围

@param started 控制共享的开始和结束的策略

@param replay 状态流的重播个数

started 接受以下的三个值:

1.Lazily: 当首个订阅者出现时开始,在scope指定的作用域被结束时终止。

2.Eagerly: 立即开始,而在scope指定的作用域被结束时终止。

3.WhileSubscribed: 这种情况有些复杂,后面会详细讲解

对于那些只执行一次的操作,您可以使用Lazily或者Eagerly。然而,如果您需要观察其他的流,就应该使用WhileSubscribed来实现细微但又重要的优化工作

3.4 Whilesubscribed策略

WhileSubscribed策略会在没有收集器的情况下取消上游数据流,通过shareIn运算符创建的SharedFlow会把数据暴露给视图 (View),同时也会观察来自其他层级或者是上游应用的数据流。

让这些流持续活跃可能会引起不必要的资源浪费,例如一直通过从数据库连接、硬件传感器中读取数据等等。当您的应用转而在后台运行时,您应当保持克制并中止这些协程。

public fun WhileSubscribed(

stopTimeoutMillis: Long = 0,

replayExpirationMillis: Long = Long.MAX_VALUE

)

如上所示,它支持两个参数:

  • 1.stopTimeoutMillis 控制一个以毫秒为单位的延迟值,指的是最后一个订阅者结束订阅与停止上游流的时间差。默认值是 0 (立即停止).这个值非常有用,因为您可能并不想因为视图有几秒钟不再监听就结束上游流。这种情况非常常见——比如当用户旋转设备时,原来的视图会先被销毁,然后数秒钟内重建。

  • 2.replayExpirationMillis表示数据重播的过时时间,如果用户离开应用太久,此时您不想让用户看到陈旧的数据,你可以用到这个参数

4. StateFlow介绍


4.1 为什么引入StateFlow

我们前面刚刚看了SharedFlow,为什么又冒出个StateFlow?

StateFlowSharedFlow 的一个比较特殊的变种,StateFlowLiveData 是最接近的,因为:

  • 1.它始终是有值的。

  • 2.它的值是唯一的。

  • 3.它允许被多个观察者共用 (因此是共享的数据流)。

  • 4.它永远只会把最新的值重现给订阅者,这与活跃观察者的数量是无关的。

可以看出,StateFlowLiveData是比较接近的,可以获取当前的值,可以想像之所以引入StateFlow就是为了替换LiveData

总结如下:

1.StateFlow继承于SharedFlow,是SharedFlow的一个特殊变种

2.StateFlowLiveData比较相近,相信之所以推出就是为了替换LiveData

4.2 StateFlow的简单使用

我们先来看看构造函数:

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

1.StateFlow构造函数较为简单,只需要传入一个默认值

2.StateFlow本质上是一个replay为1,并且没有缓冲区的SharedFlow,因此第一次订阅时会先获得默认值

3.StateFlow仅在值已更新,并且值发生了变化时才会返回,即如果更新后的值没有变化,也没会回调Collect方法,这点与LiveData不同

StateFlow类似,我们也可以用stateIn将普通流转化成SharedFlow

val result: StateFlow<Result> = someFlow

.stateIn(

scope = viewModelScope,

started = WhileSubscribed(5000),

initialValue = Result.Loading

)

shareIn类似,唯一不同的时需要传入一个默认值

同时之所以WhileSubscribed中传入了5000,是为了实现等待5秒后仍然没有订阅者存在就终止协程的功能,这个方法有以下功能

  • 用户将您的应用转至后台运行,5 秒钟后所有来自其他层的数据更新会停止,这样可以节省电量。

  • 最新的数据仍然会被缓存,所以当用户切换回应用时,视图立即就可以得到数据进行渲染。

  • 订阅将被重启,新数据会填充进来,当数据可用时更新视图。

  • 在屏幕旋转时,因为重新订阅的时间在5s内,因此上游流不会中止

4.3 在页面中观察StateFlow

LiveData类似,我们也需要经常在页面中观察StateFlow

观察StateFlow需要在协程中,因此我们需要协程构建器,一般我们会使用下面几种

  1. lifecycleScope.launch : 立即启动协程,并且在本 ActivityFragment 销毁时结束协程。

  2. LaunchWhenStartedLaunchWhenResumed,它会在lifecycleOwner进入X状态之前一直等待,又在离开X状态时挂起协程

如上图所示:

1.使用launch是不安全的,在应用在后台时也会接收数据更新,可能会导致应用崩溃

2.使用launchWhenStartedlaunchWhenResumed会好一些,在后台时不会接收数据更新,但是,上游数据流会在应用后台运行期间保持活跃,因此可能浪费一定的资源

这么说来,我们使用WhileSubscribed进行的配置岂不是无效了吗?订阅者一直存在,只有页面关闭时才会取消订阅

官方推荐repeatOnLifecycle来构建协程

在某个特定的状态满足时启动协程,并且在生命周期所有者退出该状态时停止协程,如下图所示。

比如在某个Fragment的代码中:

onCreateView(…) {

viewLifecycleOwner.lifecycleScope.launch {

viewLifecycleOwner.lifecycle.repeatOnLifecycle(STARTED) {

myViewModel.myUiState.collect { … }

}

}

}

当这个Fragment处于STARTED状态时会开始收集流,并且在RESUMED状态时保持收集,最终在Fragment进入STOPPED状态时结束收集过程。

结合使用repeatOnLifecycle APIWhileSubscribed,可以帮助您的应用妥善利用设备资源的同时,发挥最佳性能

4.4 页面中观察Flow的最佳方式

通过ViewModel暴露数据,并在页面中获取的最佳方式是:

  • ✔️ 使用带超时参数的 WhileSubscribed 策略暴露 Flow示例 1

  • ✔️ 使用 repeatOnLifecycle 来收集数据更新。示例 2

最佳实践如上图所示,如果采用其他方式,上游数据流会被一直保持活跃,导致资源浪费

当然,如果您并不需要使用到Kotlin Flow的强大功能,就用LiveData好了 😃

5 StateFlowSharedFlow有什么区别?


从上文其实可以看出,StateFlowSharedFlow其实是挺像的,让人有些傻傻分不清,有时候也挺难选择该用哪个的

我们总结一下,它们的区别如下:

  1. SharedFlow配置更为灵活,支持配置replay,缓冲区大小等,StateFlowSharedFlow的特化版本,replay固定为1,缓冲区大小默认为0

  2. StateFlowLiveData类似,支持通过myFlow.value获取当前状态,如果有这个需求,必须使用StateFlow

  3. SharedFlow支持发出和收集重复值,而StateFlowvalue重复时,不会回调collect

  4. 对于新的订阅者,StateFlow只会重播当前最新值,SharedFlow可配置重播元素个数(默认为0,即不重播)

可以看出,StateFlow为我们做了一些默认的配置,在SharedFlow上添加了一些默认约束,这些配置可能并不符合我们的要求

  1. 它忽略重复的值,并且是不可配置的。这会带来一些问题,比如当往List中添加元素并更新时,StateFlow会认为是重复的值并忽略

  2. 它需要一个初始值,并且在开始订阅时会回调初始值,这有可能不是我们想要的

  3. 它默认是粘性的,新用户订阅会获得当前的最新值,而且是不可配置的,而SharedFlow可以修改replay

StateFlow施加在SharedFlow上的约束可能不是最适合您,如果不需要访问myFlow.value,并且享受SharedFlow的灵活性,可以选择考虑使用SharedFlow

总结

简单往往意味着不够强大,而强大又常常意味着复杂,两者往往不能兼得,软件开发过程中常常面临这种取舍。

LiveData的简单并不是它的缺点,而是它的特点。StateFlowSharedFlow更加强大,但是学习成本也显著的更高.

我们应该根据自己的需求合理选择组件的使用

  1. 如果你的数据流比较简单,不需要进行线程切换与复杂的数据变换,LiveData对你来说相信已经足够了

  2. 如果你的数据流比较复杂,需要切换线程等操作,不需要发送重复值,需要获取myFlow.valueStateFlow对你来说是个好的选择

  3. 如果你的数据流比较复杂,同时不需要获取myFlow.value,需要配置新用户订阅重播无素的个数,或者需要发送重复的值,可以考虑使用SharedFlow

参考资料

Google 推荐在 MVVM 架构中使用 Kotlin Flow

Migrate from LiveData to StateFlow and SharedFlow

从 LiveData 迁移到 Kotlin 数据流

关于kotlin中的Collections、Sequence、Channel和Flow (二)

其他资料


Android综合资料(面试题,跨端开发,小程序,KT。。。):Github
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

img

img

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

总结

笔者之前工作是在金融公司可能并不是特别追求技术,而笔者又是喜欢追求技术的人,所以格格不入,只能把目标放在互联网大厂了。也希望大家都去敢于尝试和追逐自己的梦想!
BATJ大厂Android高频面试题

觉得有收获的记得点赞,关注+收藏哦!你们的点赞就是我的动力!

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

是特别追求技术,而笔者又是喜欢追求技术的人,所以格格不入,只能把目标放在互联网大厂了。也希望大家都去敢于尝试和追逐自己的梦想!
BATJ大厂Android高频面试题

[外链图片转存中…(img-xsbU5kFN-1713597039059)]

[外链图片转存中…(img-xj3rfX6Y-1713597039060)]

[外链图片转存中…(img-zld991OR-1713597039061)]

[外链图片转存中…(img-XxYxQh2k-1713597039062)]

觉得有收获的记得点赞,关注+收藏哦!你们的点赞就是我的动力!

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

在`RecyclerView`中结合`LiveData`实现数据更新通常涉及以下几个步骤: 1. 创建LiveData:首先,创建一个`LiveData<List<DataModel>>`,其中`DataModel`是您的数据模型类。这通常是从服务端获取数据的地方,或者处理本地缓存的刷新。 ```java private MutableLiveData<List<DataModel>> data MutableLiveData = new MutableLiveData<>(); ``` 2. 更新LiveData:当数据有新的变化时,比如从网络拉取新数据,或者本地数据发生更新,调用`data.postValue(newDataList)`来设置新的值。 ```java // 示例:网络请求成功后更新数据 networkRepository.getData().observe(this, new Observer<List<DataModel>>() { @Override public void onChanged(List<DataModel> newData) { data.postValue(newData); } }); ``` 3. 设置Adapter:在`onCreate`或`onResume`等适当的地方初始化`RecyclerView`,并将`LiveData`绑定给`Adapter`,通常会使用ViewModel或者LifecycleOwner来持有`LiveData`。 ```java recyclerView.setAdapter(new MyAdapter(data)); ``` 4. 适应 LiveData 的生命周期:确保当`ViewModel`或`Activity/Fragment`销毁时,取消对`LiveData`的观察,以避免内存泄漏。 ```java @Override protected void onDestroy() { super.onDestroy(); data.removeObservers(this); } ``` 5. 通知Adapter:当`LiveData`发生变化时,`Adapter`会自动收到通知并调用`notifyDataSetChanged()`来更新视图。 由于`LiveData`的设计使得数据更新过程更为简洁,只需要关注数据源,而无需手动维护数据同步的状态。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值