这一篇LiveData掉不掉价(使用->原理分析->粘性事件解决)

news2025/1/12 22:51:19

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.coloradmin.cn/o/501868.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

数据备份系列:Rsync 备份详解(二)

一、Rsync Cron 场景使用 在对数据备份要求实时性不高的情况下&#xff0c;可优先考虑该场景&#xff0c;选择一个合适的时间&#xff0c;对数据进行定时远程增量同步。 在《数据备份系列&#xff1a;Rsync 备份详解&#xff08;一&#xff09;》中我们已经对服务搭建以及远程…

【虚幻引擎】UE5数据表格导入

数据表 顾名思义&#xff0c;DataTable是一种表格&#xff0c;里面装着大量游戏相关的数据&#xff0c;这些数据会按照其含义和用途分类&#xff0c; 其中&#xff0c;数据字段可以是UObject的任意有效属性&#xff08;包括资产的引用信息&#xff09;。设计师若要将 CSV文件导…

c++类的静态变量、静态函数 笔记

正文&#xff1a; 1、看下面这个是一个常规的类 #include <iostream> #include <windows.h> using namespace std; class BOX{int callsNum1;public:BOX(){callsNum;};int fun(){return callsNum;}; }; // int BOX::callsNum1;// 程序的主函数 int main() {SetCo…

【某区护网】从外网打点到拿下域控

目录 web打点 反弹shell与权限维持 主机信息收集与反向代理 攻击域控 前端时间刚结束了攻防演练活动&#xff0c;其中一项成果为拿下某集团域控制器权限&#xff0c;直接控制域内主机5000多台。以下为攻击过程的粗略记录&#xff0c;整体来说还是比较容易。 web打点 接到…

N1Book-第一章Web入门-任意文件读取漏洞-afr_2

本题为Nu1L团队编著的《从0到1&#xff1a;CTFer成长之路》配套题目。来源网站&#xff1a;https://book.nu1l.com/ 经过多方查阅资料&#xff0c;发现题目是&#xff0c;由于Nginx配置不当产生了目录穿越漏洞。本题使用的是OpenResty&#xff0c;而OpenResty是基于Nginx与Lua实…

门诊自助打印机可以办理哪些业务呢?

自助打印机可以办理以下业务&#xff1a; 检验报告单打印&#xff1a;患者可以通过医院验单自助打印机自主打印检验报告单&#xff0c;避免了等待时间&#xff0c;提高了医院的服务效率&#xff1b;检验报告查询&#xff1a;患者可以通过医院验单自助打印机查询自己的检验报告…

HHDBCS便捷功能简介

1. 连接管理 使用数据库时&#xff0c;不可避免的要建立很多个连接。 如果单纯用命令执行切换用户的话&#xff0c;实在是一件麻烦事。 那么这种麻烦事就交给HHDECS好了。 点击连接管理&#xff0c;一键切换。 而且能在不同数据库之间随意切换 2. 使用高级模式&#xff…

Linux环境安装iperf3(网络性能测试工具)

[rootlocalhost ]# yum search iperf 已加载插件&#xff1a;fastestmirror Loading mirror speeds from cached hostfile* base: mirrors.tuna.tsinghua.edu.cn* extras: mirrors.huaweicloud.com* updates: mirrors.tuna.tsinghua.edu.cnN/S matched: iperf iperf3-devel.i6…

数据分析示例-python

数据分析示例-python 今天呢&#xff0c;博主把之前做过的一个小课题拿出来展示一下&#xff0c;当然这个课题呢做的工作量很大&#xff0c;也用到了很多可以参考的技术和代码&#xff0c;做数据分析工作的可以尝试学习学习。 这篇博客&#xff0c;我们先从数据集开始介绍。 对…

GSAP - 一款基于 JavaScript 的 web 动画库,简单几行代码就能写出丝滑流畅、高性能的动画效果

使用简单&#xff0c;但做出来的动画非常丝滑&#xff0c;也能实现很多专业的动画效果&#xff0c;推荐给大家。 关于 GSAP GSAP 的全名是 GreenSock Animation Platform&#xff0c;项目诞生非常早&#xff0c;远在 flash 繁荣的时代就存在&#xff0c;一直发展到今天已经是…

区间预测 | MATLAB实现QRBiGRU双向门控循环单元分位数回归时间序列区间预测

区间预测 | MATLAB实现QRBiGRU双向门控循环单元分位数回归时间序列区间预测 目录 区间预测 | MATLAB实现QRBiGRU双向门控循环单元分位数回归时间序列区间预测效果一览基本介绍模型描述程序设计参考资料 效果一览 进阶版 基础版 基本介绍 MATLAB实现QRBiGRU双向门控循环单…

时间序列预测(二)基于LSTM的销售额预测

时间序列预测&#xff08;二&#xff09;基于LSTM的销售额预测 小O&#xff1a;小H&#xff0c;Prophet只根据时间趋势去预测&#xff0c;会不会不太准啊 小H&#xff1a;你这了解的还挺全面&#xff0c;确实&#xff0c;销售额虽然很大程度依赖于时间趋势&#xff0c;但也会和…

YOLOv5教程-如何使用他人的数据集进行训练+测试评估模型

目录 一、前言与数据集 二、划分数据集以及配置文件的修改 1.把图片和.txt标注文件放入对应VOCData文件夹下 2..txt文件转为.xml文件 3.在VOCData目录下创建程序 split_train_val.py 并运行 4.将xml格式转为yolo_txt格式 5.设置测试文件 6.配置文件 三、聚类获得先验框 …

ABeam×StartUp | ABeam旗下艾宾信息技术开发(大连)与大连金勺科技展开合作交流

近日&#xff0c;ABeam 大中华区董事长兼总经理中野洋辅先生及艾宾信息技术开发&#xff08;大连&#xff09;&#xff08;以下简称“ABeam-TDC”&#xff09;的资深顾问团队一行人拜访了大连金勺科技有限公司&#xff08;以下简称“金勺科技”&#xff09;。 双方就各自发展的…

jsjiami.v6加密逆向分析介绍

随着互联网的不断发展&#xff0c;网站的安全性越来越受到重视。JS混淆加密技术是一种常用的保护网站安全的手段。jsjiami.v6是一款常用的JS混淆加密工具&#xff0c;下面我们来详细了解一下。 首先&#xff0c;我们来看一下一个使用jsjiami.v6进行混淆加密的案例代码&#xf…

线上 FullGC 问题排查实践 —— 手把手教你排查线上问题

一、问题发现与排查 1.1 找到问题原因 问题起因是我们收到了 jdos 的容器 CPU 告警&#xff0c;CPU 使用率已经达到 104% 观察该机器日志发现&#xff0c;此时有很多线程在执行跑批任务。正常来说&#xff0c;跑批任务是低 CPU 高内存型&#xff0c;所以此时考虑是 FullGC 引…

LC-1263. 推箱子(网格图BFS + DFS)

1263. 推箱子 难度困难105 「推箱子」是一款风靡全球的益智小游戏&#xff0c;玩家需要将箱子推到仓库中的目标位置。 游戏地图用大小为 m x n 的网格 grid 表示&#xff0c;其中每个元素可以是墙、地板或者是箱子。 现在你将作为玩家参与游戏&#xff0c;按规则将箱子 B 移…

Sleuth和zipkin

1、Sleuth是什么 为什么会出现这个技术&#xff1f;要解决哪些问题&#xff1f; 在微服务框架中&#xff0c;一个由客户端发起的请求在后端系统中会经过多个不同的的服务节点调用来协同产生最后的请求结果&#xff0c;每一个前段请求都会形成一条复杂的分布式服务调用链路&am…

2.RabbitMQ

RabbitMQ 1.初识MQ 1.1.同步和异步通讯 微服务间通讯有同步和异步两种方式&#xff1a; 同步通讯&#xff1a;就像打电话&#xff0c;需要实时响应。 异步通讯&#xff1a;就像发邮件&#xff0c;不需要马上回复。 两种方式各有优劣&#xff0c;打电话可以立即得到响应&am…

查询文件路径

1 问题 如何利用Java来查询文件的路径&#xff1f; 2 方法 1首先在类中利用main函数调用所有文件的和目录的代码。 2 然后开始写查询展示所有文件和目录的方法&#xff08;运用了递归的思想&#xff09; import java.io.File;import java.util.Arrays;import java.util.Scanner…