LiveData的post和set有什么区别?
这背后其实是 LiveData 内部针对 postValue 的一个优化机制,以及主线程消息队列处理任务的特性共同作用的结果
核心机制回顾
postValue(value) (子线程调用):
- 它不会直接修改 LiveData 的值
- 它会将
value存储到一个临时的内部变量中(我们称之为mPendingValue) - 它会检查是否已经有一个“待处理的
setValue任务”被 post 到了主线程的消息队列但尚未执行 - 关键优化: 如果没有待处理的任务,它才会创建一个
Runnable(我们称之为mPostValueRunnable),这个Runnable的工作是读取mPendingValue并调用setValue(),然后将这个Runnablepost 到主线程的MessageQueue。如果已经有一个任务在队列中等待执行,postValue不会再 post 新的任务,它仅仅更新mPendingValue就结束了 主线程Looper和MessageQueue: - 主线程有一个
Looper在不断循环,从它的MessageQueue中取出Message(其callback可能就是我们 post 的Runnable) 并执行 - 消息队列是先进先出 (FIFO) 的
- 主线程除了处理这些 post 过来的任务,还要处理 UI 绘制、用户输入等其他事件,所以队列中的任务执行可能会有延迟
覆盖场景详解
假设我们在一个后台线程 (Thread B) 中快速地连续调用 postValue,而此时主线程 (Thread A) 可能因为处理其他事务(比如 UI 绘制)而暂时繁忙:
- T1 时刻 (Thread B): 调用
liveData.postValue("Value A")。LiveData内部:mPendingValue被设为"Value A"。LiveData内部:检测到没有待处理的任务,于是创建一个mPostValueRunnable(它知道要去读取mPendingValue并调用setValue)。LiveData内部:将mPostValueRunnable添加到 Thread A 的MessageQueue。MessageQueue(Thread A):[..., mPostValueRunnable]
- T2 时刻 (Thread B, T1 后很短时间): Thread A 的
Looper还没来得及执行mPostValueRunnable,Thread B 又调用了liveData.postValue("Value B")。LiveData内部:mPendingValue被更新为"Value B"(覆盖了"Value A")。LiveData内部:检测到已经有一个mPostValueRunnable在 Thread A 的队列中等待执行。LiveData内部:因此,不再 post 新的任务。仅仅更新mPendingValue就结束了。MessageQueue(Thread A):[..., mPostValueRunnable](队列内容没变,还是只有一个任务)
- T3 时刻 (Thread B, T2 后很短时间): Thread A 仍然繁忙,
mPostValueRunnable还没执行。Thread B 再次调用liveData.postValue("Value C")。LiveData内部:mPendingValue被再次更新为"Value C"(覆盖了"Value B")。LiveData内部:检测到仍然有一个mPostValueRunnable在队列中等待。LiveData内部:仍然不 post 新的任务。MessageQueue(Thread A):[..., mPostValueRunnable](队列内容依然没变)
- T4 时刻 (Thread A): 主线程终于空闲下来,
Looper从MessageQueue中取出了在 T1 时刻 post 的那个mPostValueRunnable并开始执行它。mPostValueRunnable执行:- 它读取
LiveData内部的mPendingValue。此时mPendingValue的值是"Value C"(因为在 T3 时刻被最后更新了)。 - 它调用
liveData.setValue("Value C")。 setValue更新 LiveData 的实际值,并通知观察者,传递的值是"Value C"。 结果: 在这个过程中,虽然我们调用了三次postValue,分别是 “Value A”, “Value B”, “Value C”,但由于主线程处理mPostValueRunnable的延迟以及LiveData内部只 post 一个任务的优化,最终只有最后一个值"Value C"被真正地通过setValue设置并通知给观察者。中间的 “Value A” 和 “Value B” 在mPendingValue这个临时存储区被覆盖掉了,从未触发过setValue。
- 它读取
这就是 postValue 可能导致值覆盖的根本原因: 它是异步的,并且 LiveData 会合并(coalesce)在主线程处理之前发生的连续多次 postValue 调用,只保留最后一次的值,并通过一个任务最终更新到主线程