对于LiveData“数据倒灌”的问题,我相信很多人已经都了解了,这里提一下。所谓的“数据倒灌”:其实是类似粘性广播那样,当新的观察者开始注册观察时,会把上次发的最后一次的历史数据传递给当前注册的观察者。
比如在在下面的例子代码中:
val testViewModel = ViewModelProvider(this)[TestViewModel::class.java]
testViewModel.updateData("第一次发送数据")
testViewModel.testLiveData.observe(this,object :Observer{
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 extends MutableLiveData {
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 SingleLiveData extends MutableLiveData {
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);
}
}
这个其实就是上面 SingleLiveData
的升级版,SingleLiveData
是用一个变量控制所有的 Observer,而上面采用的每个 Observer
都采用一个控制标识进行控制。 每次 setValue
的时候,就打开所有 Observer
的开关,表示可以接受分发。分发后,关闭当前执行的 Observer
开关,即不能对其第二次执行了,除非你重新 setValue
。 这种方式基本上是比价完美了,除了内部多一个用HashMap
存放每个Observer
的标识,如果Observer
比较多的话,会有一定的内存消耗。
我们先看下LiveData
获取版本号方法:
int getVersion() {
return mVersion;
}
这个方法是一个包访问权限的方法,如果我新建一个和LiveData
同包名的类,是不是就可以不需要反射就能获取这个值呢?其实这是可行的
// 跟LiveData同包名
package androidx.lifecycle
open class SafeLiveData : MutableLiveData() {
override fun observe(owner: LifecycleOwner, observer: Observer) {
// 直接可以通过this.version获取到版本号
val pictorialObserver = PictorialObserver(observer, this.version > START_VERSION)
super.observe(owner, pictorialObserver)
}
class PictorialObserver(private val realObserver: Observer, private var preventDispatch: Boolean = false) :
Observer {
override fun onChanged(value: T) {
// 如果版本有差异,第一次不处理
if (preventDispatch) {
preventDispatch = false
return
}
realObserver.onChanged(value)
}
}
}
这种取巧的方式的思路就是:
LiveData
和Observer
是否有版本差异,如果有,第一次不响应,否则就响应我个人是偏向这种方式,也应用到了实际的开发中。这种方式的优点是:改动小,不需要反射,也不需要用HashMap
存储等,缺点是:有一定的侵入性,假如后面这个方法的访问权限修改或者包名变动,就无效了,但是我认为这种可能性是比较小,毕竟androidx库迭代了这么多版本,算是比较稳定了。
Android 性能优化篇:https://qr18.cn/FVlo89
Android Framework底层原理篇:https://qr18.cn/AQpN4J
Android 车载篇:https://qr18.cn/F05ZCM
Android 逆向安全学习笔记:https://qr18.cn/CQ5TcL
Android 音视频篇:https://qr18.cn/Ei3VPD
Jetpack全家桶篇(内含Compose):https://qr18.cn/A0gajp
OkHttp 源码解析笔记:https://qr18.cn/Cw0pBD
Kotlin 篇:https://qr18.cn/CdjtAF
Gradle 篇:https://qr18.cn/DzrmMB
Flutter 篇:https://qr18.cn/DIvKma
Android 八大知识体:https://qr18.cn/CyxarU
Android 核心笔记:https://qr21.cn/CaZQLo
Android 往年面试题锦:https://qr18.cn/CKV8OZ
2023年最新Android 面试题集:https://qr18.cn/CgxrRy
Android 车载开发岗位面试习题:https://qr18.cn/FTlyCJ
音视频面试题锦:https://qr18.cn/AcV6Ap