Flow转LiveData数据丢失原理详解
作者:TechMerger 发布时间:2023-05-20 10:22:25
前言
翻译自:arkadiuszchmura.com/posts/be-ca…
最近我在负责一段代码库,需要在使用 Flow
的 Data 层和仍然依赖 LiveData
暴露 State 数据的 UI 层之间实现桥接。好在 androidx.lifecycle
框架已经提供了一个叫做 asLiveData()
的方法,可以让你毫不费力地将 Flow
转为 LiveData
。
然而使用这种方式得到的 LiveData 需要牢记一点:在拥有一个及以上活跃的观察者的条件下,它才会发射数据。假使上游的 flow 产生了更新,但对应的 LiveData 并非活跃的状态,那么它将无法获得最新的数值。
让我通过如下的实例,向你展示我们可能会遇到的这种潜在问题。
示例
我们有一个简单的 Activity,它持有 AAC ViewModel
的实例:
class MainActivity : AppCompatActivity() {
private val viewModel: MainViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
}
该 ViewModel
的实现是这样的:
class MainViewModel : ViewModel() {
private val repository = Repository()
val state: LiveData<Int> = repository.state.asLiveData()
}
它持有一个 Repository 实例,充当琐碎的数据层。
同时 ViewModel
还通过前面提到的 asLiveData()
方法,将 Repository 持有的 StateFlow
转为了 LiveData 并对外暴露了其 State 数据。
Repository 的实现如下:
class Repository {
private val _state = MutableStateFlow(-1)
val state: StateFlow<Int> = _state
suspend fun update() {
_state.emit(Random.nextInt(until = 1000))
}
}
它拥有一个包裹着 Integer 数据(初始值为 -1)的 StateFlow
示例,同时对外提供了一个方法允许外界更新它的 State:从 0 到 1000 之间取得一个新的随机数。
试想一下,假使希望 Activity 创建的时候就能执行这个数据更新。我们可以这么实现:
在
MainViewModel
内创建一个init()
来做这个操作Activity 的
onCreate()
里调用该方法
// MainViewModel
fun init() {
// update() is suspending, so we launch a new coroutine here
viewModelScope.launch {
repository.update()
}
}
// MainActivity
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
viewModel.init()
}
这样的话,Activity 创建的时候一个新的协程将被启动,最终会调用 Repository 的 update()
,生成一个随机数并发射到它的 State。
此外,我们可能还需要在 ViewModel
中去发送包含了新生成数值的事件出去。可以在 ViewModel
中添加一个sendAnalyticalEvent()
,这样可以在执行完 Repository 的 update()
之后立即调用它。
// MainViewModel
fun init() {
viewModelScope.launch {
repository.update()
sendAnalyticalEvent() // <-- NEW
}
}
private fun sendAnalyticalEvent() {
// Typically, we would schedule a network request here
val liveDataValue = state.value
val flowValue = repository.state.value
Log.d("Current number in LiveData", "$liveDataValue")
Log.d("Current number in StateFlow", "$flowValue")
}
该方法内,我们可以做些典型的操作,比如向后端服务器发送网络请求。这里,让我们仅仅在 Logcat 里打印来自 LiveData
and Flow
的数值即可。
上面的运行结果相当出乎意料。你可能会争辩道:LiveData
没有获取到最新的数值,是因为没有足够的时间从上游的 flow 中收集数据,不然的话肯定能够拿到正确的数值。
但这个 case 里,不仅仅是 LiveData
获得到的是错误的数值,它获得到的是 null。而且请别忘了,它的存放在 Repository 里的初值是 -1。这只能代表一个意思:这里的 LiveData
压根没有从 StateFlow
里收集任何数据。
原因是我们还没有开始观察这个 LiveData
,它自然会被当作是非活跃的。而且根据 asLiveData()
方法的文档可以知道,在这种情况下 LiveData
不会从上游的 flow 收集任何数据。
asLiveData:Creates a LiveData that has values collected from the origin Flow.
上游 flow 数据的收集发生在 LiveData
变成活跃的时候,即 LiveData.onActive
。如果 flow 尚未完成,而 LiveData
变成了非激活状态,即 LiveData.onActive
,那么 flow 的数据收集将在timeoutInMs
参数指定的时间后被取消。除非在超时之前,LiveData
变成活跃状态。
一旦我们开始在 Activity 里观察 LiveData
的数据(因此将促使 LiveData 变成活跃状态),它就能够拥有正确的、最新的数值了。
// MainActivity
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
viewModel.init()
viewModel.state.observe(this) { // <-- NEW
Log.d("Current number in MainActivity", "$it")
}
}
如下是 Logcat 里新的输出。
上面的示例里,我们采用的是 StateFlow
,但规则同样适用于 SharedFlow
。
而且,情况将更加糟糕,因为当 LiveData
处于非激活状态的时候,任何发送给 SharedFlow
的事件都将永久丢失(默认情况下 SharedFlow
不会将任何数值重新发送给新的订阅者)。
来源:https://juejin.cn/post/7186249265138794551


猜你喜欢
- 数组:数组可以用来保存多个基本数据类型的数据,也可以用来保存多个对象。数组的长度是不可改变的,一旦初始化数组时就指定了数组的长度(无论是静态
- Command 常用属性CommText 要下达至数据源的命令CommanTimeout 出错等待时间Command 三种方法Execute
- 使用场景1、将用户信息导出为excel表格(导出数据....)2、将Excel表中的信息录入到网站数据库(习题上传....)大大减轻网站录入
- 本文实例讲述了Java面向对象程序设计:抽象类,接口用法。分享给大家供大家参考,具体如下:本文内容:抽象类接口抽象类与接口的异同
- package org.itat.stax;import java.io.IOException;import java.io.InputS
- 本文实例讲述了Android编程获取通知栏高度的方法。分享给大家供大家参考,具体如下:这里通过反射机制获取通知栏高度通知栏高度写在dimen
- 本文实例讲述了C#线性渐变画刷LinearGradientBrush用法。分享给大家供大家参考。具体如下:using System;usin
- 最近.一个朋友跟我说想,我给她弄个闹钟APP软件...功能其实很简单...只需要弄个简单的闹钟.自己设计设计时间.然后时间到了的时候,闹铃放
- 写在前面: 从一个窗体的创建显示,再到与用户的交互,最后窗体关闭,这中间经历过了一系列复杂的过程,本文将从Winform应用程序中的Prog
- 本文实例讲述了Android编程实现的手写板和涂鸦功能。分享给大家供大家参考,具体如下:下面仿一个Android手写板和涂鸦的功能,直接上代
- 首先定义布局文件:<?xml version="1.0" encoding="utf-8"?&
- 通常,我们会被要求实现类似支付宝首页的特效:随着界面的滑动,标题栏的背景透明度渐变。在实际开发中,常见的滑动有列表RecyclerView(
- 0x00 关于AndroidManifest.xmlAndroidManifest.xml 是每个android程序中必须的文件。它位于整个
- 一、背景项目中肯定会遇到异步调用其他方法的场景,比如有个计算过程,需要计算很多个指标的值,但是每个指标计算的效率快慢不同,如果采用同步执行的
- RecyclerView 是 android-support-v7-21 版本中新增的一个 Widgets, 还有一个 CardView 会
- Java 定时器在JAVA中实现定时器功能要用的二个类是Timer,TimerTaskTimer类是用来执行任务的类,它接受一个
- redisson的几大特性相信看了这个标题的同学,对这个问题以已经非常不陌生了,信手拈来redisson的几大特性:可重入性【多个业务线同一
- 一、序言在日常一线开发过程中,总有列表转树的需求,几乎是项目的标配,比方说做多级菜单、多级目录、多级分类等,有没有一种通用且跨项目的解决方式
- 范例说明Android的Widget,有许多是为了与User交互而特别设计的,但也有部分是作为程序提示、显示程序运行状态的Widget。现在
- 固定的策略有时候还是无法满足千变万化的需求变动,一方面需要支持特定的用户需求,另一方面又得尽可能的复用代码,避免重复开发,这就需要将这部分的