⏰ : 全文字数: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
的时分,假如 LiveData
的 mVersion
大于 ObserverWrapper
的 mLastVersion
,LiveData
就会强制把当时 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)
}
}
}
这种取巧的办法的思路便是:
- 利用同包名拜访权限能够获取版别号,不需求经过反射获取
- 判别
LiveData
和Observer
是否有版别差异,假如有,第一次不响应,否则就响应
我个人是偏向这种办法,也应用到了实际的开发中。这种办法的优点是:改动小,不需求反射,也不需求用HashMap
存储等,缺陷是:有必定的侵入性,假如后面这个办法的拜访权限修正或许包名变化,就无效了,可是我以为这种可能性是比较小,究竟androidx库迭代了这么多版别,算是比较稳定了。