⏰ : 全文字数:5500+

: 内容关键字:LiveData数据倒灌
: 公_众_号:七郎的小院

数据倒灌现象

关于LiveData“数据倒灌”的问题,我相信很多人已经都了解了,这儿提一下。所谓的“数据倒灌”:其实是相似粘性播送那样,当新的观察者开始注册观察时,会把前次发的最后一次的历史数据传递给当时注册的观察者

比如在在下面的例子代码中:

val testViewModel = ViewModelProvider(this)[TestViewModel::class.java]
testViewModel.updateData("第一次发送数据")
testViewModel.testLiveData.observe(this,object :Observer<String>{
    override fun onChanged(value: String) {
        println("==============$value")
    }
})

updateData办法发送了一次数据,当下面调用LiveData的observe办法时,会立即打印==============第一次发送数据,这便是上面说的“数据倒灌”现象。

产生原因

原因其实也很简单,其实便是 LiveData内部有一个mVersion字段,记录版别,其初始的 mVersion 是-1,当咱们调用了其 setValue 或许 postValue,其 mVersion+1;关于每一个观察者的封装 ObserverWrapper,其初始 mLastVersion 也为-1,也便是说,每一个新注册的观察者,其 mLastVersion 为-1;当 LiveData 设置这个 ObserverWrapper 的时分,假如 LiveDatamVersion 大于 ObserverWrappermLastVersionLiveData 就会强制把当时 value 推送给 Observer

也便是下面这段代码

    private void considerNotify(ObserverWrapper observer) {
        if (!observer.mActive) {
            return;
        }
        if (!observer.shouldBeActive()) {
            observer.activeStateChanged(false);
            return;
        }
        // 判别observer的版别是否大于LiveData的版别mVersion
        if (observer.mLastVersion >= mVersion) {
            return;
        }
        observer.mLastVersion = mVersion;
        observer.mObserver.onChanged((T) mData);
    }

所以要处理这个问题,思路上有两种办法:

  • 经过改变每个ObserverWrapper的版别号的值
  • 经过某种办法,保证第一次分发不响应

处理办法

现在网络上能够看到有三种处理办法

每次只响应一次

public class SingleLiveData<T> extends MutableLiveData<T> {
    private final AtomicBoolean mPending = new AtomicBoolean(false);
    public SingleLiveData() {
    }
    public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer) {
        super.observe(owner, (t) -> {
            if (this.mPending.compareAndSet(true, false)) {
                observer.onChanged(t);
            }
        });
    }
    @MainThread
    public void setValue(@Nullable T t) {
        this.mPending.set(true);
        super.setValue(t);
    }
    @MainThread
    public void call() {
        this.setValue((Object)null);
    }
}

这个办法能处理历史数据往回发的问题,可是关于多Observe监听就不行了,只能单个监听,假如是多个监听,只有一个能正常收到,其他的就无法正常工作

反射

这种办法便是每次注册观察者时,经过反射获取LiveData的版别号,然后又经过反射修正当时Observer的版别号值。这种办法的优点是:

  • 能够多 Observer 监听
  • 处理粘性问题

可是也有缺陷:

  • 每次注册 observer 的时分,都需求反射更新版别,耗时有功能问题

UnPeekLiveData

public class UnPeekLiveData extends LiveData {
    protected boolean isAllowNullValue;
    private final HashMap observers = new HashMap();
    public void observeInActivity(@NonNull AppCompatActivity activity, @NonNull Observer super T> observer) {
        LifecycleOwner owner = activity;
        Integer storeId = System.identityHashCode(observer);
        observe(storeId, owner, observer);
    }
    private void observe(@NonNull Integer storeId,
                         @NonNull LifecycleOwner owner,
                         @NonNull Observer super T> observer) {
        if (observers.get(storeId) == null) {
            observers.put(storeId, true);
        }
        super.observe(owner, t -> {
            if (!observers.get(storeId)) {
                observers.put(storeId, true);
                if (t != null || isAllowNullValue) {
                    observer.onChanged(t);
                }
            }
        });
    }
    @Override
    protected void setValue(T value) {
        if (value != null || isAllowNullValue) {
            for (Map.Entry entry : observers.entrySet()) {
                entry.setValue(false);
            }
            super.setValue(value);
        }
    }
    protected void clear() {
        super.setValue(null);
    }
}

这个其实便是上面 SingleLiveData 的升级版,SingleLiveData 是用一个变量操控一切的 Observer,而上面选用的每个 Observer 都选用一个操控标识进行操控。
每次 setValue 的时分,就翻开一切 Observer 的开关,表示能够承受分发。分发后,封闭当时履行的 Observer 开关,即不能对其第2次履行了,除非你重新 setValue
这种办法基本上是比价完美了,除了内部多一个用HashMap寄存每个Observer的标识,假如Observer比较多的话,会有必定的内存消耗。

新的思路

咱们先看下LiveData获取版别号办法:

int getVersion() {
    return mVersion;
}

这个办法是一个包拜访权限的办法,假如我新建一个和LiveData同包名的类,是不是就能够不需求反射就能获取这个值呢?其实这是可行的

// 跟LiveData同包名
package androidx.lifecycle
open class SafeLiveData<T> : MutableLiveData<T>() {
    override fun observe(owner: LifecycleOwner, observer: Observer<in T>) {
        // 直接能够经过this.version获取到版别号
        val pictorialObserver = PictorialObserver(observer, this.version > START_VERSION)
        super.observe(owner, pictorialObserver)
    }
    class PictorialObserver<T>(private val realObserver: Observer<in T>, private var preventDispatch: Boolean = false) :
        Observer<T> {
        override fun onChanged(value: T) {
            // 假如版别有差异,第一次不处理
            if (preventDispatch) {
                preventDispatch = false
                return
            }
            realObserver.onChanged(value)
        }
    }
}

这种取巧的办法的思路便是:

  • 利用同包名拜访权限能够获取版别号,不需求经过反射获取
  • 判别LiveDataObserver是否有版别差异,假如有,第一次不响应,否则就响应

我个人是偏向这种办法,也应用到了实际的开发中。这种办法的优点是:改动小,不需求反射,也不需求用HashMap存储等,缺陷是:有必定的侵入性,假如后面这个办法的拜访权限修正或许包名变化,就无效了,可是我以为这种可能性是比较小,究竟androidx库迭代了这么多版别,算是比较稳定了。