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 的数值即可。

Flow转LiveData数据丢失原理详解

上面的运行结果相当出乎意料。你可能会争辩道: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 里新的输出。

Flow转LiveData数据丢失原理详解

上面的示例里,我们采用的是 StateFlow,但规则同样适用于 SharedFlow

而且,情况将更加糟糕,因为当 LiveData 处于非激活状态的时候,任何发送给 SharedFlow 的事件都将永久丢失(默认情况下 SharedFlow 不会将任何数值重新发送给新的订阅者)。

来源:https://juejin.cn/post/7186249265138794551

标签:Flow,LiveData,数据丢失
0
投稿

猜你喜欢

  • C#中异步Socket通信编程代码实例

    2022-02-23 11:29:59
  • 如何优雅的进行Spring整合MongoDB详解

    2022-10-23 05:24:19
  • Struts2拦截器Interceptor的原理与配置实例详解

    2022-06-23 17:34:45
  • 利用Android画圆弧canvas.drawArc()实例详解

    2022-05-21 08:45:54
  • 3种Android隐藏顶部状态栏及标题栏的方法

    2022-01-14 12:48:43
  • C#使用ILGenerator动态生成函数的简单代码

    2022-07-10 01:41:06
  • Java实现人脸识别登录、注册等功能(最新完整版)

    2022-09-25 00:20:13
  • java8 多个list对象用lambda求差集操作

    2022-02-19 06:26:52
  • C#调用Oracle存储过程的方法

    2022-10-19 02:29:19
  • Java后端Tomcat实现WebSocket实例教程

    2023-08-22 17:31:09
  • C语言实现线性表的基本操作详解

    2022-02-24 22:08:32
  • 使用Jitpack发布开源Java库的详细流程

    2021-12-18 06:55:58
  • 详解Android中Handler的使用方法

    2023-05-03 06:35:30
  • springboot下mybatis-plus如何打印sql日志和参数到日志文件

    2023-06-15 12:34:05
  • Android仿京东分类模块左侧分类条目效果

    2022-09-09 04:35:34
  • 详解Android开发数据持久化之文件存储(附源码)

    2022-06-19 20:30:33
  • 详解Java如何进行Base64的编码(Encode)与解码(Decode)

    2023-01-31 18:53:34
  • 详解Spring全局异常处理的三种方式

    2023-11-11 15:31:47
  • Flutter进阶之实现动画效果(六)

    2023-09-15 15:13:50
  • Android中关于百度糯米app关闭网页或窗口的方法(99%人不知)

    2023-02-18 00:23:49
  • asp之家 软件编程 m.aspxhome.com