Android重学系列(三):LiveData你应该要知道的那些事儿

1,216 阅读8分钟

一、概述

Google官网是这样描述LiveData的:

LiveData 是一种可观察的数据存储器类。与常规的可观察类不同,LiveData 具有生命周期感知能力,这种感知能力可确保 LiveData 仅更新处于活跃生命周期状态的应用组件观察者。

同时,官方也对它的注意点进行了说明

通常,LiveData 仅在数据发生更改时才发送更新,并且仅发送给活跃观察者。此行为的一种例外情况是,观察者从非活跃状态更改为活跃状态时也会收到更新。此外,如果观察者第二次从非活跃状态更改为活跃状态,则只有在自上次变为活跃状态以来值发生了更改时,它才会收到更新。

也就是我们可以得出一条结论,即LiveData数据观察者在以下场景下才能接收到通知

  1. 数据发生了改变
  2. 非活跃状态活跃状态(前提是数据发生了改变)

上面的结论还有一个前提是,我们只是调用了常规的observe,而不是observeForeverobserveForever这个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呢,在那之前,我觉得我们还应该理解两个概念,即statusaction

状态和操作的理解

这两个概念有点类似于当下很火的一个议题,即”命令式UI“”声明式UI“,站在UI的角度理解status和action,我们可以这么理解

  1. status是控件内部UI状态的一系列描述,UI根据若干个的status去组合显示,不管外界环境发生了变化,如果status没有进行改变,则UI对外的显示则保持不变,如我们去执行UI的刷新、重绘等,对用户来说并没有发生改变,更宽泛的来说,就是status是事物组成的一部分,如我们描述一个人的健康状态,精神状态,它都属于人这个对象
  2. 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[手动狗头]