Android LiveData 处理数据倒灌的几种措施
LiveData
- MutableLiveData
- SingleLiveEvent
- UnFlowLiveData
- UnPeekLiveData
- 扩展
- 1. 为什么Fragment中要使用`viewLifecycleOwner`代替`this`
- 2. 如果使用了`Fragment`的`this`,有可能产生的问题
- 参考地址
MutableLiveData
-
粘性特性
- 定义
MutableLiveData
的粘性特性是指当一个观察者开始观察MutableLiveData
时,如果数据已经有了一个值,那么这个观察者会立即收到这个已有值的通知。例如,假设MutableLiveData
存储了一个用户的偏好设置(如主题颜色),在观察者(如一个Activity用于显示界面)开始观察之前,这个主题颜色的值可能已经被设置好了。当Activity开始观察该MutableLiveData
时,它会立刻获取到这个已有的主题颜色值,就好像数据“粘”在了观察者上。
- 实现原理
- 当
MutableLiveData
的observe()
方法被调用时,它会检查当前是否已经有了一个非空的值。如果有,它会立即将这个值传递给新注册的观察者。在内部机制上,LiveData
(MutableLiveData
的父类)有一个版本号机制。每次数据更新时,版本号会增加。当观察者注册时,会比较观察者的初始版本号和LiveData
的当前版本号,如果LiveData
的版本号大于观察者的初始版本号,并且有数据值,就会将数据发送给观察者。
- 当
- 应用场景
- 这种特性在很多场景下都很有用。比如在应用启动时加载配置数据。如果
MutableLiveData
存储了应用的语言配置,当一个新的Activity启动并开始观察这个语言配置数据时,它可以立即获取到已有的语言配置,从而正确地设置界面语言,无需额外的操作来获取初始配置。
- 这种特性在很多场景下都很有用。比如在应用启动时加载配置数据。如果
- 定义
-
数据倒灌
- 定义
- 数据倒灌是指在某些情况下,当配置发生变化(如屏幕旋转)导致Activity或Fragment重建时,观察者可能会收到旧的数据。例如,一个Activity中有一个
MutableLiveData
存储用户输入的表单数据。当屏幕旋转时,Activity会被重建,新的观察者(重建后的Activity)可能会收到之前旧的观察者已经接收过的数据,就好像数据“倒灌”回来了。
- 数据倒灌是指在某些情况下,当配置发生变化(如屏幕旋转)导致Activity或Fragment重建时,观察者可能会收到旧的数据。例如,一个Activity中有一个
- 产生原因
- 这主要是因为
MutableLiveData
的粘性特性和Android系统的组件重建机制共同作用的结果。在组件重建时,新的观察者会重新注册到MutableLiveData
上,由于粘性特性,它会检查是否有已有的数据并可能接收这些数据,而这些数据可能是之前旧的观察者已经处理过的数据。
- 这主要是因为
- 解决方法
- 为了避免数据倒灌,可以采用一些策略。一种常见的方法是使用
SingleLiveEvent
(它是对MutableLiveData
的一种特殊应用),SingleLiveEvent
在发送一次事件后会自动重置状态,这样可以避免在组件重建时旧事件被重新发送。另一种方法是在观察者中记录数据是否已经被处理过,通过比较数据的版本号或者其他唯一标识来判断是否应该接收数据,避免重复处理之前已经处理过的数据。
- 为了避免数据倒灌,可以采用一些策略。一种常见的方法是使用
- 定义
SingleLiveEvent
解决了数据倒灌的问题
是对 Event 事件包装器 一致性问题的改进,但未解决多观察者消费的问题;
而且额外引入了消息未能从内存中释放的问题。
public class SingleLiveEvent<T> extends MutableLiveData<T> {
private static final String TAG = "SingleLiveEvent";
private final AtomicBoolean mPending = new AtomicBoolean(false);
@MainThread
public void observe(LifecycleOwner owner, final Observer<T> observer) {
if (hasActiveObservers()) {
Log.w(TAG, "Multiple observers registered but only one will be notified of changes.");
}
// Observe the internal MutableLiveData
super.observe(owner, new Observer<T>() {
@Override
public void onChanged(@Nullable T t) {
if (mPending.compareAndSet(true, false)) {
observer.onChanged(t);
}
}
});
}
@MainThread
public void setValue(@Nullable T t) {
mPending.set(true);
super.setValue(t);
}
/**
* Used for cases where T is Void, to make calls cleaner.
*/
@MainThread
public void call() {
setValue(null);
}
}
关于LiveData粘性事件所带来问题的解决方案:https://www.jianshu.com/p/d0244c4c7cc9
简单粗暴解决LiveData『数据倒灌』的问题:https://blog.csdn.net/hewuzhao/article/details/117165379
UnFlowLiveData
解决了数据倒灌,并且支持多个观察者
方案思路:
-
在observe/observeForever时创建新的LiveData,并且根据observer保存该LiveData到mObserverMap中,而且该LiveData订阅相关的observer;
-
当postValue/setValue时,遍历mObserverMap的所有LiveData,并把值设置给LiveData;
public class UnFlowLiveData<T> {
private final Handler mMainHandler;
private T mValue;
private final ConcurrentHashMap<Observer<? super T>, MutableLiveData<T>> mObserverMap;
public UnFlowLiveData() {
mMainHandler = new Handler(Looper.getMainLooper());
mObserverMap = new ConcurrentHashMap<>();
}
@MainThread
public void observeForever(@NonNull Observer<? super T> observer) {
checkMainThread("observeForever");
MutableLiveData<T> liveData = new MutableLiveData<>();
// 该LiveData也observeForever该observer,这样setValue时,能把value回调到onChanged中
liveData.observeForever(observer);
mObserverMap.put(observer, liveData);
}
@MainThread
public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer) {
checkMainThread("observe");
Lifecycle lifecycle = owner.getLifecycle();
if (lifecycle.getCurrentState() == DESTROYED) {
// ignore
return;
}
lifecycle.addObserver(new LifecycleObserver() {
@OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
public void onDestroy() {
mObserverMap.remove(observer);
lifecycle.removeObserver(this);
}
});
MutableLiveData<T> liveData = new MutableLiveData<>();
// 该LiveData也observe该observer,这样setValue时,能把value回调到onChanged中
liveData.observe(owner, observer);
mObserverMap.put(observer, liveData);
}
@MainThread
public void removeObserver(@NonNull final Observer<? super T> observer) {
checkMainThread("removeObserver");
mObserverMap.remove(observer);
}
public T getValue() {
return mValue;
}
public void clearValue() {
mValue = null;
}
@MainThread
public void setValue(T value) {
checkMainThread("setValue");
mValue = value;
// 遍历所有LiveData,并把value设置给LiveData
for (MutableLiveData<T> liveData : mObserverMap.values()) {
liveData.setValue(value);
}
}
public void postValue(T value) {
mMainHandler.post(() -> setValue(value));
}
private void checkMainThread(String methodName) {
if (Looper.myLooper() != Looper.getMainLooper()) {
throw new IllegalStateException("UnFlowLiveData, Cannot invoke " + methodName
+ " on a background thread");
}
}
}
简单粗暴解决LiveData『数据倒灌』的问题:https://blog.csdn.net/hewuzhao/article/details/117165379
UnPeekLiveData
public class ProtectedUnPeekLiveData<T> extends LiveData<T> {
protected boolean isAllowNullValue;
private final HashMap<Integer, Boolean> observers = new HashMap<>();
public void observeInActivity(@NonNull AppCompatActivity activity, @NonNull Observer<? super T> observer) {
LifecycleOwner owner = activity;
Integer storeId = System.identityHashCode(observer);//源码这里是activity.getViewModelStore(),是为了保证同一个ViewModel环境下"唯一可信源"
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<Integer, Boolean> entry : observers.entrySet()) {
entry.setValue(false);
}
super.setValue(value);
}
}
protected void clear() {
super.setValue(null);
}
}
其思路也很清晰,为每个传入的observer对象携带一个布尔类型的值,作为其是否能进入observe方法的开关。每当有一个新的observer存进来的时候,开关默认关闭。
每次setValue后,打开所有Observer的开关,允许所有observe执行。
同时方法进去后,关闭当前执行的observer开关,即不能对其第二次执行了,除非你重新setValue。
作者:慕尼黑凌晨四点
链接:https://www.jianshu.com/p/d0244c4c7cc9
扩展
以下是为你格式化后并且添加代码标注说明的内容:
1. 为什么Fragment中要使用viewLifecycleOwner
代替this
在Android开发中,Fragment
与Fragment
中的View
的生命周期并不一致。我们在使用一些可观察的数据(比如LiveData
)时,需要让观察者(observer
)准确感知Fragment
中的View
的生命周期,而不是Fragment
本身的生命周期。基于这样的需求,Android专门构造了与Fragment
中的View
相对应的LifecycleOwner
,也就是viewLifecycleOwner
。以下是相关代码示例说明其重要性:
// 假设这是一个Fragment类
public class MyFragment extends Fragment {
private MutableLiveData<String> liveData = new MutableLiveData<>();
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
// 错误示范:使用this(也就是Fragment自身)来作为LifecycleOwner观察LiveData
liveData.observe(this, new Observer<String>() {
@Override
public void onChanged(String s) {
// 处理数据变化逻辑
}
});
// 正确示范:使用viewLifecycleOwner来作为LifecycleOwner观察LiveData
liveData.observe(viewLifecycleOwner, new Observer<String>() {
@Override
public void onChanged(String s) {
// 处理数据变化逻辑,这样能确保和View的生命周期更好地绑定
}
});
}
}
来源信息:
- 作者:caz
- 链接:https://juejin.cn/post/6915222252506054663
- 来源:稀土掘金
- 著作权说明:著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
2. 如果使用了Fragment
的this
,有可能产生的问题
当在Fragment
中使用“this
”(指代Fragment
自身)来处理相关逻辑时,存在一定风险。在Fragment
未被复用的情况下,可能不会出现明显问题。但是一旦Fragment
被复用,LiveData
内的数据就会交由多个页面共同处理,这极有可能对其他页面的内部逻辑产生不良影响。以下是一个简单示例来体现这种情况:
// 假设有两个不同的页面(这里简化为两个Fragment)复用了同一个Fragment类
public class ReusedFragment extends Fragment {
private MutableLiveData<Integer> sharedLiveData = new MutableLiveData<>();
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 模拟设置LiveData的初始值
sharedLiveData.setValue(10);
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
// 错误示范:使用this观察LiveData,当Fragment被复用就可能出问题
sharedLiveData.observe(this, new Observer<Integer>() {
@Override
public void onChanged(Integer integer) {
// 这里不同页面都会执行这个逻辑,可能互相干扰
Log.d("ReusedFragment", "Received data: " + integer);
}
});
}
}
// 第一个复用ReusedFragment的页面(Fragment)
public class FirstPageFragment extends Fragment {
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
ReusedFragment reusedFragment = new ReusedFragment();
getChildFragmentManager().beginTransaction().add(R.id.container, reusedFragment).commit();
}
}
// 第二个复用ReusedFragment的页面(Fragment)
public class SecondPageFragment extends Fragment {
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
ReusedFragment reusedFragment = new ReusedFragment();
getChildFragmentManager().beginTransaction().add(R.id.container, reusedFragment).commit();
}
}
在上述代码中,如果ReusedFragment
中的LiveData
使用this
来添加观察者,那么当它在FirstPageFragment
和SecondPageFragment
中被复用时,LiveData
数据变化的通知会同时传递给两个页面中的观察者,导致它们的内部逻辑可能因为共享的数据处理而出现混乱,互相影响。
参考地址
关于LiveData粘性事件所带来问题的解决方案:https://www.jianshu.com/p/d0244c4c7cc9
简单粗暴解决LiveData『数据倒灌』的问题:https://blog.csdn.net/hewuzhao/article/details/117165379
豆包AI