一、概述
Google官网是这样描述LiveData的:
LiveData 是一种可观察的数据存储器类。与常规的可观察类不同,LiveData 具有生命周期感知能力,这种感知能力可确保 LiveData 仅更新处于活跃生命周期状态的应用组件观察者。
同时,官方也对它的注意点进行了说明
通常,LiveData 仅在数据发生更改时才发送更新,并且仅发送给活跃观察者。此行为的一种例外情况是,观察者从非活跃状态更改为活跃状态时也会收到更新。此外,如果观察者第二次从非活跃状态更改为活跃状态,则只有在自上次变为活跃状态以来值发生了更改时,它才会收到更新。
也就是我们可以得出一条结论,即LiveData数据观察者在以下场景下才能接收到通知
数据发生了改变- 从
非活跃状态到活跃状态(前提是数据发生了改变)
上面的结论还有一个前提是,我们只是调用了常规的observe,而不是observeForever,observeForever这个Api直接忽略了observe的生命周期判断逻辑
源码中是如何处理值改变时的监听的?
值得注意的是,上面提到的数据发生了改变,并不是我们想的值发生改变才会通知,而是只要你调用了LiveData的setValue(),就默认为数据发生了改变,此结论可从源码中得到佐证
如下面是调用setValue()
protected void setValue(T value) {
assertMainThread("setValue");
mVersion++;
mData = value;
dispatchingValue(null);
}
这里只是判断了一下线程情况,然后把mVersion值+1,重新赋值了一下value,就直接走dispatch方法了,可见只要是调用了setValue,那就肯定会被认为存储的值产生了变化
接着往下跟踪dispatchingValue(),发现最终会走到considerNotify(),去通知单个观察者,这里就对观察者的生命周期状态,以及数据的版本mVersion进行了判断
private void considerNotify(ObserverWrapper observer) {
//如果不处于活跃状态,则直接return
if (!observer.mActive) {
return;
}
//这里主要是更新最新的观察者状态(此时处于非活跃状态)
if (!observer.shouldBeActive()) {
observer.activeStateChanged(false);
return;
}
//版本是否发生变化,直白了讲就是是否调用了setValue()
if (observer.mLastVersion >= mVersion) {
return;
}
//通知观察者,数据更新了
observer.mLastVersion = mVersion;
observer.mObserver.onChanged((T) mData);
}
好了,前面其实差不多就把LiveData介绍完了,那我们在业务开发里,怎样去使用LiveData呢,在那之前,我觉得我们还应该理解两个概念,即status和action
状态和操作的理解
这两个概念有点类似于当下很火的一个议题,即”命令式UI“和”声明式UI“,站在UI的角度理解status和action,我们可以这么理解
- status是
控件内部UI状态的一系列描述,UI根据若干个的status去组合显示,不管外界环境发生了变化,如果status没有进行改变,则UI对外的显示则保持不变,如我们去执行UI的刷新、重绘等,对用户来说并没有发生改变,更宽泛的来说,就是status是事物组成的一部分,如我们描述一个人的健康状态,精神状态,它都属于人这个对象 - action则定义的是
一次操作和行为,操作是不可逆而且不能自我重复的,例如让一个人去做一件事,那么这就是一个action
然后我们回到LiveData,就可以感觉到LiveData是为status而生的,但在我们的日常开发中,虽然mvvm式的编程,status是我们的核心重点,但是也无法避免需要面对action的情况。
如我们可能根据某些业务的结果,需要进行弹窗1次,做1次清理操作等等,这里我们强调次数这个概念,因为action对次数较为敏感,理论上一个action,永远只会触发1次,除非我们重复的发起action
很好的理解status和action,我们才能使用LiveData得心应手,同时可以规避一系列问题,如大家经常说的”数据倒灌“问题,”数据丢失“问题
SingleLiveEvent来实现普通的Action
依我们对Action的定义,那显然目前的LiveData并不能满足我们的要求,不过在网络上早就有了解决方案,下面贴上代码
class SingleLiveEvent<T> : MutableLiveData<T?>() {
private val mPending = AtomicBoolean(false)
@MainThread
override fun observe(owner: LifecycleOwner, observer: Observer<in T?>) {
if (hasActiveObservers()) {
LogUtil.d("Multiple observers registered but only one will be notified of changes.")
}
super.observe(owner, { t ->
//当前值与预期值true相等时,返回true,并且重置值为false
if (mPending.compareAndSet(true, false)) {
observer.onChanged(t)
}
})
}
@MainThread
override fun setValue(t: T?) {
mPending.set(true)
super.setValue(t)
}
其实代码很简单,就是设置了一个标志位,拦截了observe逻辑,只要标志位mPending=true时,才会回调观察者,并且再将标志位mPending重置为false,这样就保证了我们更新值后,永远只会触发1次观察者回调的监听。
不过这种方案会有个小问题,就是只支持一个观察者,当多个观察者同时观察时,数据变更同样只会通知1个观察者,其实知道了上面的处理,这个小问题我们稍加改动下即可支持多个观察者。
MultiLiveEvent来支持多个观察者的Action
我们可以直接通过ObserverWrapper来包装一下我们的观察者Observer,这样就实现了整体控制到单个观察者控制的转变,也就修复了上面不能支持多个观察者的缺陷
open class MultiLiveEvent<T>: MediatorLiveData<T>() {
private val observers = ArraySet<ObserverWrapper<in T>>()
@MainThread
override fun observe(owner: LifecycleOwner, observer: Observer<in T>) {
//判断一下是否重复observe
observers.find { it.observer === observer }?.let { _ -> // existing
return
}
val wrapper = ObserverWrapper(observer)
observers.add(wrapper)
super.observe(owner, wrapper)
}
//省略removeObserver的逻辑
@MainThread
override fun setValue(t: T?) {
observers.forEach { it.newValue() }
super.setValue(t)
}
private class ObserverWrapper<T>(val observer: Observer<T>) : Observer<T> {
private var pending = AtomicBoolean(false)
override fun onChanged(t: T?) {
if (pending.compareAndSet(true, false)) {
observer.onChanged(t)
}
}
fun newValue() {
pending.set(true)
}
}
}
LiveData的一些封装
前面提到,在LiveData setValue时会进行线程判断
protected void setValue(T value) {
assertMainThread("setValue");
mVersion++;
mData = value;
dispatchingValue(null);
}
非主线程调用,这里会直接抛出异常,可能平常自己业务没问题,但当我们是服务的提供方,提供Api来让其他人来调用时,可能就会出现这种线程问题,其实我们这里完全可以进行一些简单的封装,来增大我们提供Api的健壮性
我们同样创建一个包装类,不过这次是包装的数据和我们LiveData自身
private class SetValueRunnable<T>(
private val liveData: MutableLiveData<T>,
val data: T
) : Runnable {
override fun run() {
liveData.value = data
}
companion object {
fun <T> create(@NonNull liveData: MutableLiveData<T>, data: T): SetValueRunnable<T> {
return SetValueRunnable(liveData, data)
}
}
}
然后我们就可以间接使用Handler来切换线程
object LiveDataUtil {
private var sMainHandler: Handler? = null
fun <T> setValue(mld: MutableLiveData<T>?, d: T) {
if (mld == null) {
return
}
if (Thread.currentThread() === Looper.getMainLooper().thread) {
mld.setValue(d)
} else {
postSetValue(mld, d)
}
}
private fun <T> postSetValue(mld: MutableLiveData<T>, d: T) {
if (sMainHandler == null) {
sMainHandler = Handler(Looper.getMainLooper())
}
sMainHandler?.post(SetValueRunnable.create(mld, d))
}
}
这样,我们每次更新LiveData的值时,都调用LiveDataUtil.setValue(xx,true)这样,就可以不考虑线程的问题了,甚至我们还可以结合Handler,来实现一些延时的Api
fun <T> postDelaySetValue(mld: MutableLiveData<T>, d: T, what: Int, delayMillis: Long) {
if (sMainHandler == null) {
sMainHandler = Handler(Looper.getMainLooper())
}
val obtain = Message.obtain(sMainHandler, SetValueRunnable.create(mld, d))
obtain.what = what
sMainHandler?.sendMessageDelayed(obtain, delayMillis)
}
fun removeMessage(what: Int) {
sMainHandler?.removeMessages(what)
}
为什么不使用LiveData postValue
到这里,其实一切看起来没什么问题对吧? 可能熟悉LiveData的同学心里会有个小问题,LiveData不是已经内置了postValue()吗?为啥不直接判断线程后,子线程就直接用这个Api
为什么?其实官方也在注释中也特意提到了这个问题
If you called this method multiple times before a main thread executed a posted task, only the last value would be dispatched 意思是,如果我们多次调用postValue,会导致只会回调最后1次的更新结果
至于为什么?我们来看下源码
protected void postValue(T value) {
boolean postTask;
synchronized (mDataLock) {
postTask = mPendingData == NOT_SET;
mPendingData = value;
}
if (!postTask) {
return;
}
ArchTaskExecutor.getInstance().postToMainThread(mPostValueRunnable);
}
可以看到,这里有一个postTask标志位,如果是false,就直接return出去了。
可以看到它的赋值地方,mPendingData默认值就是NOT_SET,所以第一次postValue,postTask=true,然后就将mPendingData赋值给了最新我们设置的值,那第2次进来,就触发了 postTask=false,也就不会执行postToMainThread()了,这个时候如果mPostValueRunnable没有被消费掉,就会造成后面post的值覆盖了之前的值,只会产生一次更新回调。
那我们再来看看mPendingData是什么时候被消费掉的,直接看mPostValueRunnable
private final Runnable mPostValueRunnable = new Runnable() {
@Override
public void run() {
Object newValue;
synchronized (mDataLock) {
newValue = mPendingData;
mPendingData = NOT_SET;
}
//noinspection unchecked
setValue((T) newValue);
}
};
可以看到,当mPostValueRunnable被执行时,直接将mPendingData = NOT_SET,然后调用了setValue方法,那其实就和我们的实现逻辑差不多。
到这里,可以看到我们的方案和官方的方案差不了多少,殊途同归,都是为了解决部分使用场景的产物,场景和要求不同,很难得出哪个是正确的,比如我们可能只关心最近1次的结果,不需要每次action都去做1次操作,官方的做法反而间接的实现了防抖的效果。
不过也从侧面看出,官方的设计方案是严谨的,例如如果观察者不在活跃状态,多次setValue,最后也还是只会有1次数据更新的回调。但略有不同的是,对于生命周期的场景,官方提供了observeForever来解决。而postValue的场景,似乎没有好的支持,只能我们自己手动实现一个
总结
本文并没有介绍LiveData的详细使用姿势,主要是因为官方文档里已经写的很全了,大家可以自行查阅,主要是举例说明了几个开发中我们经常忽视的小细节,比如status和action的理解、LiveData来实现action的一些缺点和解决方案,相信掌握了这些细节,更能让我们不再”彷徨“,在日常的业务开发中,少写bug[手动狗头]