LiveData 是 Android 架构组件中的一个类,用于观察数据的变化并在界面发生变化时更新 UI。然而,在使用 LiveData 时可能会遇到数据倒灌(Data Inundation)的问题,即观察者在订阅时会立即收到一个数据更新,这有时可能不是预期行为。
1. 示例
所谓的“数据倒灌”:其实是类似粘性广播那样,当新的观察者开始注册观察时,会把上次发的最后一次的历史数据传递给当前注册的观察者。
val liveData = MutableLiveData<String>()
liveData.postValue("在添加观察者之前发送数据")
liveData.observe(lifecycleOwner, Observer<String> { value ->
Log.d("TAG", "onChanged: $value")
})
在添加观察者之前发送了一条数据,当调用 liveData.observe
方法后,观察者会立即收到一条数据更新。
2. 发生原因
mVersion
和 mLastVersion
这两个值的变化是数据倒灌的核心原因之一。
mVersion
mVersion
是 LiveData 内部维护的一个版本号,初始值为 -1,每次数据更新(setValue)时会递增。
public abstract class LiveData<T> {
static final int START_VERSION = -1;
private int mVersion;
public LiveData() {
mVersion = START_VERSION;
}
protected void setValue(T value) {
mVersion++;
dispatchingValue(null);
}
}
mLastVersion
mLastVersion
是每个观察者持有的版本号,用于记录该观察者最后一次接收到的数据版本,只有 mLastVersion
的值小于 mVersion
才会通知观察者。
public abstract class LiveData<T> {
static final int START_VERSION = -1;
private abstract class ObserverWrapper {
int mLastVersion = START_VERSION;
}
void dispatchingValue(@Nullable ObserverWrapper initiator) {
// ...
considerNotify(initiator);
}
private void considerNotify(ObserverWrapper observer) {
// ...
if (observer.mLastVersion >= mVersion) {
return;
}
observer.mLastVersion = mVersion;
observer.mObserver.onChanged((T) mData);
}
}
分析
- 在创建LiveData 对象时,
mVersion
初始化为 -1。 - 在注册新的观察者之前发送了一条数据:LiveData 中的
mVersion
值会加 1,即 mVersion 值为 0。 - 在注册新的观察者后:新的观察者中的
mLastVersion
为 初始值 -1。 - 注册新的观察者后:如果当前页面处于活跃状态,会调用一次dispatchingValue方法,由于此时
mLastVersion
值小于mVersion
,LiveData 会立即将当前的数据发送给这个新的观察者。
3. 具体场景分析
常见的LiveData数据倒灌场景:
Fragment 切换:
在使用 ViewPager 或 FragmentTransaction 切换 Fragment 时,如果多个 Fragment 共享同一个 ViewModel,重新显示的 Fragment 可能会收到 LiveData 的数据倒灌。
配置变化(如屏幕旋转):
当设备发生配置变化时(如屏幕旋转),Activity 或 Fragment 会重新创建。此时重新订阅 LiveData 会导致数据倒灌,显示旧数据。
短生命周期组件
比如使用 LiveData 的对话框、弹出菜单等组件,在重新显示或创建时会重新订阅 LiveData,导致数据倒灌。
具体分析
这里,我们以Fragment 切换场景为例,分析 mVersion
和 mLastVersion
的变化,从而更好的理解数据倒灌发生的原因。
1. 初始化和首次订阅
当第一个 Fragment 创建并订阅 LiveData 时,LiveData 的 mVersion
可能是初始值 -1,该 Fragment 中观察者 observer
的 mLastVersion
也为初始值 -1。
2. 数据更新
当 LiveData 的数据发生变化时,mVersion
会递增(例如变为 0),并通知所有活跃的观察者更新数据。这时,所有活跃观察者的 mLastVersion
也会更新为 0。
3. Fragment 切换
当第一个 Fragment 切换到第二个 Fragment,第二个 Fragment 开始订阅 LiveData。因为第二个 Fragment 是新创建的观察者,其 mLastVersion
初始值为 -1。由于此时 LiveData 的 mVersion
为 0,而新的观察者 mLastVersion
为 -1,导致新观察者会立即收到最新的数据更新(即数据倒灌)。
4. 解决方法
1. 单一事件模式(SingleLiveEvent)
创建一个只发送一次事件的 LiveData 类,确保事件只被消费一次。
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 (mPending.compareAndSet(true, false)) {
observer.onChanged(t);
}
});
}
@MainThread
public void setValue(@Nullable T t) {
mPending.set(true);
super.setValue(t);
}
}
2. 使用反射
通过反射获取 LiveData 的版本号,然后通过反射修改当前 Observer 的版本号。
public class UnPeekLiveData<T> extends LiveData<T> {
private final HashMap<Observer<? super T>, Boolean> 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<Observer<? super T>, Boolean> entry : observers.entrySet()) {
entry.setValue(false);
}
super.setValue(value);
}
}
protected void clear() {
super.setValue(null);
}
}
3. 同包名
通过创建一个与 LiveData 同包名的类,可以直接访问 getVersion
方法,而不需要通过反射获取版本号。
// 与 LiveData 同包名
package androidx.lifecycle;
public class SafeLiveData<T> extends MutableLiveData<T> {
@Override
public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer) {
// 直接可以通过 this.version 获取到版本号
PictorialObserver pictorialObserver = new PictorialObserver(observer, this.version > START_VERSION);
super.observe(owner, pictorialObserver);
}
private static class PictorialObserver<T> implements Observer<T> {
private final Observer<? super T> realObserver;
private boolean preventDispatch;
PictorialObserver(Observer<? super T> realObserver, boolean preventDispatch) {
this.realObserver = realObserver;
this.preventDispatch = preventDispatch;
}
@Override
public void onChanged(T value) {
if (preventDispatch) {
preventDispatch = false;
return;
}
realObserver.onChanged(value);
}
}
}
这种方法利用同包名访问权限获取版本号,不需要反射,改动小,性能好。但如果后续 AndroidX 库的访问权限或包名修改,则需要调整代码。