likes
comments
collection
share

面试刨根问底系列。为什么LiveData多线程频繁postValue会丢失数据?

作者站长头像
站长
· 阅读数 72

liveData多线程postValue会导致数据丢失,想必各位看官应该都有点印象。要么开发实际遇到过,要么博文里读到过。 如果面试官问你,为什么会这样? 我:emmm...因为google就是这样设计的,人家就不建议你频繁去刷新值。

这个问题可能有点离谱,一般也不会被问到。年轻的我甚至觉得这面试官有病吧,这都问... 后来成长后的我深挖了源码,恍然大悟,沃茨奥原来是这样。

刨根问底直奔主题,我们直接打开LiveData源码

首先LiveData支持在多线程进行postValue,且线程安全。 postValue方法一进来就获取,然后在更新mPendingData(待定值)。想不安全都难。 面试刨根问底系列。为什么LiveData多线程频繁postValue会丢失数据? 有锁加持那怎么还会出现丢失数据的问题呢? postValue只保证了更新值的代码块线程安全(也就是绿色框框)。 后面就切换到了主线程进行后续的数据分发操作。 这里涉及到了postValue的调用线程(更新值),和主线程进行分发值。 典型的生产者消费者模型

  • 生产者:更新值
  • 消费者:分发值

理想的情况下,生产和消费的速率匹配,按部就班什么问题都没有。但是如果生产者更新值速率过快。消费者分发至速率过低,就会导致上一次更新的值,还没有被分发出去,又有新的值更新,最终导致LiveData丢失中途更新的部分值。 注意:生产者更新完最后一个值。后面就不会出现,消费者没有分发完,值被更新的情况。所以LiveData一定能接收到postValue的最终值。

小结:为什么多线程postValue可能丢失值:

因为更新和分发值,分别在两个不同的线程。可能存在生产和消费速率不匹配问题(也可以理解成上下游,流速不匹配)。

为什么LiveData要这样设计

切换成主线程分发数据,可以保证LiveData数据都是在主线程回调。免去开发者自己切换线程,方便使用。 看上去是个比较合理的原因,那真的只是为了切线程方便吗?我们接着往下看。 跟进一下ArchTaskExecutorpostToMainThread方法。

面试刨根问底系列。为什么LiveData多线程频繁postValue会丢失数据? 打开ArchTaskExecutor 源码,我们会发现他的postToMainThread,其实是调用的mDelegate的方法。而mDelegate又是mDefaultTaskExecutor 面试刨根问底系列。为什么LiveData多线程频繁postValue会丢失数据?

那我们直接看mDefaultTaskExecutorpostToMainThread 他以一种我们不常见的方式,创建了一个拥有主线程Looper的handler 面试刨根问底系列。为什么LiveData多线程频繁postValue会丢失数据?

mMainHandler = createAsync(Looper.getMainLooper());

createAsync从方法名,我们很容易分辨出他会创建一个异步的。 那么一个拥有主线程Looper的异步Handler,会用来干嘛呢? 对的他是用来发送异步消息的。

val handler = Handler(Looper.getMainLooper())

主线程的异步消息,我们很容易联想到一个词同步屏障。 MvvM,LiveData本身就是推荐作为驱动UI的数据流供我们使用的。postValue中的createAsync异步消息和View刷新渲染一个情况异曲同工。遇到同步屏障,阻塞同步消息,优先处理异步消息。妙啊知识居然闭环了。

这里说的handler异步消息与下面的postDelayed完全是两码事。

Handler(Looper.getMainLooper()).postDelayed({
    Log.d(TAG, "巴拉巴拉: ")
}, 3000)

小结:所以为什么要这样设计

为了遇到同步屏障的时候依旧可以优先处理,刷新UI。相应的如果有过多的异步消息,可能会影响性能开销,甚至卡顿。所以LiveData才不建议使用在过于频繁刷新值的场景。

LiveData 单靠自身无法解决上下游流速不匹配问题(频繁刷新值的场景),处理这样的场景可以考虑使用kotlin 提供的Flow。

转载自:https://juejin.cn/post/7373489827244769316
评论
请登录