这一篇LiveData掉不掉价(使用+粘性事件解决)
1. 简介
LiveData 是一种可观察的数据存储器类。与常规的可观察类不同,LiveData 具有生命周期感知能力,意指它遵循其他应用组件(如 activity、fragment 或 service)的生命周期。这种感知能力可确保 LiveData 仅更新处于活跃生命周期状态的应用组件观察者。
2. 特性介绍
如果观察者(由 Observer 类表示)的生命周期处于 STARTED 或 RESUMED 状态,则 LiveData 会认为该观察者处于活跃状态。LiveData 只会将更新通知给活跃的观察者。为观察 LiveData 对象而注册的非活跃观察者不会收到更改通知。您可以注册与实现 LifecycleOwner 接口的对象配对的观察者。有了这种关系,当相应的 Lifecycle 对象的状态变为 DESTROYED 时,便可移除此观察者。这对于 activity 和 fragment 特别有用,因为它们可以放心地观察 LiveData 对象,而不必担心泄露(当 activity 和 fragment 的生命周期被销毁时,系统会立即退订它们)。
3. 优点
确保界面符合数据状态
LiveData 遵循观察者模式。当底层数据发生变化时,LiveData 会通知 Observer 对象。您可以整合代码以在这些 Observer 对象中更新界面。这样一来,您无需在每次应用数据发生变化时更新界面,因为观察者会替您完成更新。
不会发生内存泄漏
观察者会绑定到 Lifecycle 对象,并在其关联的生命周期遭到销毁后进行自我清理。
不会因 Activity 停止而导致崩溃
如果观察者的生命周期处于非活跃状态(如返回堆栈中的 activity),它便不会接收任何 LiveData 事件。
不再需要手动处理生命周期
界面组件只是观察相关数据,不会停止或恢复观察。LiveData 将自动管理所有这些操作,因为它在观察时可以感知相关的生命周期状态变化。
不再需要手动处理生命周期
界面组件只是观察相关数据,不会停止或恢复观察。LiveData 将自动管理所有这些操作,因为它在观察时可以感知相关的生命周期状态变化。
数据始终保持最新状态
( <–注意这个 – 粘性事件)
如果生命周期变为非活跃状态,它会在再次变为活跃状态时接收最新的数据。例如,曾经在后台的 Activity 会在返回前台后立即接收最新的数据。
适当的配置更改
( <–注意这个–数据倒灌)
如果由于配置更改(如设备旋转)而重新创建了 Activity 或 Fragment,它会立即接收最新的可用数据。
共享资源
x 您可以使用单例模式扩展 LiveData 对象以封装系统服务,以便在应用中共享它们。LiveData 对象连接到系统服务一次,然后需要相应资源的任何观察者只需观察 LiveData 对象。
使用:
1.创建LiveData对象,一般结合ViewModel使用,在ViewModel中创建
public class MyViewModel extends ViewModel {
private MutableLiveData<Integer> currentSecond;
public MutableLiveData<Integer> getCurrentSecond() {
if(currentSecond == null){
currentSecond = new MutableLiveData<>();
currentSecond.setValue(0);
}
return currentSecond;
}
}
2.在Activity中使用:
public class MainActivity extends AppCompatActivity {
private MyViewModel viewModel;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TextView textView = findViewById(R.id.textView);
viewModel = new ViewModelProvider(this, new ViewModelProvider.AndroidViewModelFactory(getApplication())).get(MyViewModel.class);
textView.setText(String.valueOf(viewModel.getCurrentSecond().getValue()));
viewModel.getCurrentSecond().observe(this, new Observer<Integer>() {
/**
* 在这里更新UI,每次setValue或者postValue时会回调到这个方法
*/
@Override
public void onChanged(Integer i) {
textView.setText(String.valueOf(i));
}
});
startTimer(); //开启定时任务
}
/*
* 子线程定时周期任务,每隔一秒更新数据
*/
private void startTimer() {
new Timer().schedule(new TimerTask() {
@Override
public void run() {
//非UI线程 postValue
//UI线程 setValue
viewModel.getCurrentSecond().postValue(viewModel.getCurrentSecond().getValue()+1);
}
},1000,1000);
}
}
运行就能发现数据每秒都会+1了。
5. 常见方法
setValue
–> 主线程更新value时可以使用
postValue
–> 子线程中更新value时可以使用
postValue最后还是通过主线程的Handler切换到主线程调用setValue
getValue
--> 获取liveData的值
observe
--> 第一个参数是LifecyclerOwner(是livedata监听生命周期的关键),第二个参数传入一个Observer接口的实例化对象(需重写onchanged方法,更新UI的逻辑写到这个方法中)
public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer)
6. 工作原理:
每次改变LiveData数据都会对数据版本号加1,并触发版本号小于数据版本号的观察者监听,触发后观察者的版本号与数据版本号一致。 (观察者版本号从-1开始)
7.注意事项
粘性事件:(复用同一个LiveData时会发生)
更新数据后,观察者再订阅,新注册的观察者版本号为-1小于数据版本号,所以注册时会触发一次数据监听。
数据倒灌
:
由于LiveData的激活状态标识先变为false,再变为true,导致触发小于数据版本号的所有观察者的监听。
常见场景为:使用ViewModel持有LivaData,并在生命周期内创建监听对象,则在Activity由于屏幕翻转等配置变化引发onDestroy时,ViewModel不会执行clear,因此保留了内部的LiveData,而在生命周期内重新创建监听对象的版本号为-1,所以在onStart之后会触发观察者监听。
上述俩种现象的发生原理都在于LiveData的工作原理。
8. 引发粘性事件和数据倒灌的相关源码
LiveData.observe方法:
@MainThread
public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer) {
assertMainThread("observe");
if (owner.getLifecycle().getCurrentState() == DESTROYED) {
// 状态不是活跃态则return
return;
}
/**
*将传入的Observer对象封装成LifecycleBoundObserver
*LifecycleBoundObserver继承自ObserverWrapper(内部定义了mLastVersion)
* int mLastVersion = START_VERSION(-1);
*/
LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer);
//判断observer实例是否存在(如果我们复用同一个observer,这里就不为null)
ObserverWrapper existing = mObservers.putIfAbsent(observer, wrapper);
if (existing != null && !existing.isAttachedTo(owner)) {
throw new IllegalArgumentException("Cannot add the same observer"
+ " with different lifecycles");
}
if (existing != null) {
return;
}
owner.getLifecycle().addObserver(wrapper);
}
最终会走到LiveData的considerNotify方法:
private void considerNotify(ObserverWrapper observer) {
//判断状态是不是活跃状态
if (!observer.mActive) {
return;
}
if (!observer.shouldBeActive()) {
observer.activeStateChanged(false);
return;
}
/**
*此处就是关键了,如果上一次的版本号大于等于当前的版本号,才执行return
* 但我们每次调用observe的时候都是new Observe(),因此mLastVersion都是为-1的
* 所以都是小于mVersion,会触发后续的操作
*/
if (observer.mLastVersion >= mVersion) {
return;
}
/**
* 更新当前observer的版本号,并且回调onChanged方法
*/
observer.mLastVersion = mVersion;
observer.mObserver.onChanged((T) mData);
}
再来看看mVersion是什么时候改变的:
protected void setValue(T value) {
assertMainThread("setValue");
mVersion++; //在调用setValue(postValue)的时候,LiveData的mVersion都会+1
mData = value;
dispatchingValue(null); //这里会走到上面的considerNotify去触发onChanged方法
}
总结: 我们有时候在复用LiveData实例的时候,只要调用了postValue或setValue,那么后续在调用observe的时候,只要传入的是new Observe.. 那么就会触发onChanged方法。
9.粘性事件和数据倒灌的解决方案
不要复用LiveData实例
复用同一个Observer (不同的Activity中Observer的onChanged写的逻辑一般不同,因此我们一般不会复用同一个Observer) ,
上述俩个方法只能说是从工作原理分析看来不会产生上述问题,但是达不到真正的需求目的。 (有时候就是需要复用LiveData实例,并且绝大多数情况都是不会去复用同一个Observer的)
认真的解决方案--非Hook版本(网上的hook版本我都觉得解决不了)
/**
* 非粘性的LiveData
*
* @param <T>
*/
public class UnPeekLiveData<T> extends MutableLiveData<T> {
private int mVersion = 0;//被观察者的版本
private int observerVersion = 0;//观察者的版本
@Override
public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer) {
//每次订阅的时候,先把版本同步
observerVersion = mVersion;
super.observe(owner, new Observer<T>() {
@Override
public void onChanged(T t) {
if (mVersion != observerVersion) {
observer.onChanged(t);
}
}
});
}
@MainThread
public void setValue(T value) {
mVersion++;
super.setValue(value);
}
}
其实粘性事件和数据倒灌可以说是LiveData的特性,可能LiveData设计初衷就是想保证数据的最新性,因此每次订阅调用observe的时候都会先通过回调拿到最新的LiveData最新的数据,因此LiveData的粘性和非粘性得看具体场景分析到底需不需要解决该情况。 这里只做大概了解有这些情况而已。