当前位置: 首页 > article >正文

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

更多用法:官方文档


http://www.kler.cn/news/17936.html

相关文章:

  • 亚马逊云科技开启您的云财务管理之旅:云财务运营
  • 电子招标采购系统源码之什么是电子招投标系统?
  • JavaScript的ES6
  • 作者等级与权益说明
  • 系统分析师之系统设计(十五)
  • 成为数据分析师,需要具备哪些技能?
  • 米哈游测开岗 【一面总结】
  • FT2000+ qemu kvm openEuer crash 分析 频繁设置CPU online及cgroup导致进程卡死、不调度故障
  • Go数据结构---可变长数组
  • 正则表达式 - 字符组
  • 牛客 BM18 二维数组中的查找
  • c# 数据保存为PDF(二) (Aspose pdf篇)
  • Linux C/C++后台开发面试重点知识
  • 互联网摸鱼日报(2023-05-08)
  • 虚拟环境中的 CPU 优化
  • YAPI--撰写接口文档的平台
  • ruby环境中的irb
  • 奇数单增序列
  • 有限等待忙等、让权等待死等、互斥遵循的几大原则——参考《天勤操作系统》,柳婼的博客
  • 基于C#开发 B/S架构的实验室管理系统 云LIS系统(MVC + SQLserver + Redis)
  • HTTP的特点
  • Python入门(三)变量和简单数据类型(二)
  • MySQL基础(十四)视图
  • 设计模式——模板方法模式
  • 数据结构与算法基础(王卓)(35):交换排序之快排【第二阶段:标准答案、初步发现问题】
  • 看不懂具体的代码方法?这样向chatgpt提问
  • (22)目标检测算法之 yolov8模型导出总结
  • Scala Option类型,异常处理,IO,高阶函数
  • Ceph入门到精通-OSD 故障排除
  • TCP/IP相关面试题