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);@MainThreadpublic 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 MutableLiveDatasuper.observe(owner, new Observer<T>() {@Overridepublic void onChanged(@Nullable T t) {if (mPending.compareAndSet(true, false)) {observer.onChanged(t);}}});}@MainThreadpublic void setValue(@Nullable T t) {mPending.set(true);super.setValue(t);}/*** Used for cases where T is Void, to make calls cleaner.*/@MainThreadpublic 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<>();}@MainThreadpublic 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);}@MainThreadpublic void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer) {checkMainThread("observe");Lifecycle lifecycle = owner.getLifecycle();if (lifecycle.getCurrentState() == DESTROYED) {// ignorereturn;}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);}@MainThreadpublic void removeObserver(@NonNull final Observer<? super T> observer) {checkMainThread("removeObserver");mObserverMap.remove(observer);}public T getValue() {return mValue;}public void clearValue() {mValue = null;}@MainThreadpublic void setValue(T value) {checkMainThread("setValue");mValue = value;// 遍历所有LiveData,并把value设置给LiveDatafor (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);}}});}@Overrideprotected 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<>();@Overridepublic void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {super.onViewCreated(view, savedInstanceState);// 错误示范:使用this(也就是Fragment自身)来作为LifecycleOwner观察LiveDataliveData.observe(this, new Observer<String>() {@Overridepublic void onChanged(String s) {// 处理数据变化逻辑}});// 正确示范:使用viewLifecycleOwner来作为LifecycleOwner观察LiveDataliveData.observe(viewLifecycleOwner, new Observer<String>() {@Overridepublic 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<>();@Overridepublic void onCreate(@Nullable Bundle savedInstanceState) {super.onCreate(savedInstanceState);// 模拟设置LiveData的初始值sharedLiveData.setValue(10);}@Overridepublic void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {super.onViewCreated(view, savedInstanceState);// 错误示范:使用this观察LiveData,当Fragment被复用就可能出问题sharedLiveData.observe(this, new Observer<Integer>() {@Overridepublic void onChanged(Integer integer) {// 这里不同页面都会执行这个逻辑,可能互相干扰Log.d("ReusedFragment", "Received data: " + integer);}});}
}// 第一个复用ReusedFragment的页面(Fragment)
public class FirstPageFragment extends Fragment {@Overridepublic 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 {@Overridepublic 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