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的粘性和非粘性得看具体场景分析到底需不需要解决该情况。 这里只做大概了解有这些情况而已。

更多用法:官方文档

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,下面是一个简单的示例: 首先,在build.gradle文件中添加以下依赖项: ```groovy // ViewModel and LiveData implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0' // Data Binding implementation 'androidx.databinding:databinding-runtime:4.0.1' ``` 接下来,创建一个名为MainActivity的Activity,并在其布局文件中添加两个Fragment的占位符: activity_main.xml: ```xml <layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"> <FrameLayout android:id="@+id/container" android:layout_width="match_parent" android:layout_height="match_parent"/> </layout> ``` MainActivity.java: ```java public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main); // 加载第一个Fragment getSupportFragmentManager().beginTransaction() .replace(R.id.container, new FirstFragment()) .commit(); } } ``` 接下来,创建一个名为FirstFragment的Fragment,并在其布局文件中使用DataBinding绑定数据: first_fragment.xml: ```xml <layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <TextView android:id="@+id/textView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{viewModel.text}" /> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Next" android:onClick="@{viewModel::onNextClicked}" /> </LinearLayout> </layout> ``` FirstFragment.java: ```java public class FirstFragment extends Fragment { private FirstViewModel viewModel; @Nullable @Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { // 使用DataBinding绑定布局文件 FirstFragmentBinding binding = DataBindingUtil.inflate(inflater, R.layout.first_fragment, container, false); // 创建ViewModel实例 viewModel = ViewModelProviders.of(this).get(FirstViewModel.class); // 将ViewModel与布局文件中的变量绑定 binding.setViewModel(viewModel); // 设置LifecycleOwner,以便LiveData知道何时更新UI binding.setLifecycleOwner(this); return binding.getRoot(); } } ``` 下面是FirstViewModel.java: ```java public class FirstViewModel extends ViewModel { private MutableLiveData<String> textLiveData = new MutableLiveData<>(); public FirstViewModel() { // 初始化LiveData的默认值 textLiveData.setValue("Hello, World!"); } public LiveData<String> getText() { return textLiveData; } public void onNextClicked() { // 更新LiveData的值 textLiveData.setValue("Next Clicked!"); } } ``` 最后,创建另一个名为SecondFragment的Fragment,并在MainActivity中添加一个方法来加载它: second_fragment.xml: ```xml <layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <TextView android:id="@+id/textView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{viewModel.text}" /> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Previous" android:onClick="@{viewModel::onPreviousClicked}" /> </LinearLayout> </layout> ``` SecondFragment.java: ```java public class SecondFragment extends Fragment { private SecondViewModel viewModel; @Nullable @Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { // 使用DataBinding绑定布局文件 SecondFragmentBinding binding = DataBindingUtil.inflate(inflater, R.layout.second_fragment, container, false); // 创建ViewModel实例 viewModel = ViewModelProviders.of(this).get(SecondViewModel.class); // 将ViewModel与布局文件中的变量绑定 binding.setViewModel(viewModel); // 设置LifecycleOwner,以便LiveData知道何时更新UI binding.setLifecycleOwner(this); return binding.getRoot(); } } ``` 下面是SecondViewModel.java: ```java public class SecondViewModel extends ViewModel { private MutableLiveData<String> textLiveData = new MutableLiveData<>(); public SecondViewModel() { // 初始化LiveData的默认值 textLiveData.setValue("Goodbye, World!"); } public LiveData<String> getText() { return textLiveData; } public void onPreviousClicked() { // 更新LiveData的值 textLiveData.setValue("Previous Clicked!"); } } ``` 最后,在MainActivity中添加一个方法来加载SecondFragment: ```java private void loadSecondFragment() { getSupportFragmentManager().beginTransaction() .replace(R.id.container, new SecondFragment()) .commit(); } ``` 现在,你的MVVM Android项目就完成了!你可以使用LiveData和ViewModel来管理数据,并使用DataBinding将数据绑定到UI上。Lifecycle组件可确保UI在活动和片段之间正确地进行管理。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值