简单使用如下:
//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
?
StateFlow
是 SharedFlow
的一个比较特殊的变种,StateFlow
与 LiveData
是最接近的,因为:
-
1.它始终是有值的。
-
2.它的值是唯一的。
-
3.它允许被多个观察者共用 (因此是共享的数据流)。
-
4.它永远只会把最新的值重现给订阅者,这与活跃观察者的数量是无关的。
可以看出,StateFlow
与LiveData
是比较接近的,可以获取当前的值,可以想像之所以引入StateFlow
就是为了替换LiveData
总结如下:
1.StateFlow
继承于SharedFlow
,是SharedFlow
的一个特殊变种
2.StateFlow
与LiveData
比较相近,相信之所以推出就是为了替换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
需要在协程中,因此我们需要协程构建器,一般我们会使用下面几种
-
lifecycleScope.launch
: 立即启动协程,并且在本Activity
或Fragment
销毁时结束协程。 -
LaunchWhenStarted
和LaunchWhenResumed
,它会在lifecycleOwner
进入X
状态之前一直等待,又在离开X
状态时挂起协程
如上图所示:
1.使用launch
是不安全的,在应用在后台时也会接收数据更新,可能会导致应用崩溃
2.使用launchWhenStarted
或launchWhenResumed
会好一些,在后台时不会接收数据更新,但是,上游数据流会在应用后台运行期间保持活跃,因此可能浪费一定的资源
这么说来,我们使用WhileSubscribed
进行的配置岂不是无效了吗?订阅者一直存在,只有页面关闭时才会取消订阅
官方推荐repeatOnLifecycle
来构建协程
在某个特定的状态满足时启动协程,并且在生命周期所有者退出该状态时停止协程,如下图所示。
比如在某个Fragment
的代码中:
onCreateView(…) {
viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.lifecycle.repeatOnLifecycle(STARTED) {
myViewModel.myUiState.collect { … }
}
}
}
当这个Fragment
处于STARTED
状态时会开始收集流,并且在RESUMED
状态时保持收集,最终在Fragment
进入STOPPED
状态时结束收集过程。
结合使用repeatOnLifecycle API
和WhileSubscribed
,可以帮助您的应用妥善利用设备资源的同时,发挥最佳性能
4.4 页面中观察Flow
的最佳方式
通过ViewModel
暴露数据,并在页面中获取的最佳方式是:
最佳实践如上图所示,如果采用其他方式,上游数据流会被一直保持活跃,导致资源浪费
当然,如果您并不需要使用到Kotlin Flow
的强大功能,就用LiveData
好了 😃
5 StateFlow
与SharedFlow
有什么区别?
从上文其实可以看出,StateFlow
与SharedFlow
其实是挺像的,让人有些傻傻分不清,有时候也挺难选择该用哪个的
我们总结一下,它们的区别如下:
-
SharedFlow
配置更为灵活,支持配置replay
,缓冲区大小等,StateFlow
是SharedFlow
的特化版本,replay
固定为1,缓冲区大小默认为0 -
StateFlow
与LiveData
类似,支持通过myFlow.value
获取当前状态,如果有这个需求,必须使用StateFlow
-
SharedFlow
支持发出和收集重复值,而StateFlow
当value
重复时,不会回调collect
-
对于新的订阅者,
StateFlow
只会重播当前最新值,SharedFlow
可配置重播元素个数(默认为0,即不重播)
可以看出,StateFlow
为我们做了一些默认的配置,在SharedFlow
上添加了一些默认约束,这些配置可能并不符合我们的要求
-
它忽略重复的值,并且是不可配置的。这会带来一些问题,比如当往
List
中添加元素并更新时,StateFlow
会认为是重复的值并忽略 -
它需要一个初始值,并且在开始订阅时会回调初始值,这有可能不是我们想要的
-
它默认是粘性的,新用户订阅会获得当前的最新值,而且是不可配置的,而
SharedFlow
可以修改replay
StateFlow
施加在SharedFlow
上的约束可能不是最适合您,如果不需要访问myFlow.value
,并且享受SharedFlow
的灵活性,可以选择考虑使用SharedFlow
总结
–
简单往往意味着不够强大,而强大又常常意味着复杂,两者往往不能兼得,软件开发过程中常常面临这种取舍。
LiveData
的简单并不是它的缺点,而是它的特点。StateFlow
与SharedFlow
更加强大,但是学习成本也显著的更高.
我们应该根据自己的需求合理选择组件的使用
-
如果你的数据流比较简单,不需要进行线程切换与复杂的数据变换,
LiveData
对你来说相信已经足够了 -
如果你的数据流比较复杂,需要切换线程等操作,不需要发送重复值,需要获取
myFlow.value
,StateFlow
对你来说是个好的选择 -
如果你的数据流比较复杂,同时不需要获取
myFlow.value
,需要配置新用户订阅重播无素的个数,或者需要发送重复的值,可以考虑使用SharedFlow
参考资料
Google 推荐在 MVVM 架构中使用 Kotlin Flow
Migrate from LiveData to StateFlow and SharedFlow
关于kotlin中的Collections、Sequence、Channel和Flow (二)
其他资料
Android综合资料(面试题,跨端开发,小程序,KT。。。):Github
最后
这里附上上述的技术体系图相关的几十套腾讯、头条、阿里、美团等公司2021年的面试题,把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节,由于篇幅有限,这里以图片的形式给大家展示一部分。
相信它会给大家带来很多收获:
当程序员容易,当一个优秀的程序员是需要不断学习的,从初级程序员到高级程序员,从初级架构师到资深架构师,或者走向管理,从技术经理到技术总监,每个阶段都需要掌握不同的能力。早早确定自己的职业方向,才能在工作和能力提升中甩开同龄人。
- 无论你现在水平怎么样一定要 持续学习 没有鸡汤,别人看起来的毫不费力,其实费了很大力,这四个字就是我的建议!!!
- 我希望每一个努力生活的IT工程师,都会得到自己想要的,因为我们很辛苦,我们应得的。
当我们在抱怨环境,抱怨怀才不遇的时候,没有别的原因,一定是你做的还不够好!
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门,即可获取!
[外链图片转存中…(img-LnEK1cjx-1715256451211)]
当程序员容易,当一个优秀的程序员是需要不断学习的,从初级程序员到高级程序员,从初级架构师到资深架构师,或者走向管理,从技术经理到技术总监,每个阶段都需要掌握不同的能力。早早确定自己的职业方向,才能在工作和能力提升中甩开同龄人。
- 无论你现在水平怎么样一定要 持续学习 没有鸡汤,别人看起来的毫不费力,其实费了很大力,这四个字就是我的建议!!!
- 我希望每一个努力生活的IT工程师,都会得到自己想要的,因为我们很辛苦,我们应得的。
当我们在抱怨环境,抱怨怀才不遇的时候,没有别的原因,一定是你做的还不够好!
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门,即可获取!