Android LiveData 处理数据倒灌的几种措施

LiveData

  • MutableLiveData
  • SingleLiveEvent
  • UnFlowLiveData
  • UnPeekLiveData
  • 扩展
      • 1. 为什么Fragment中要使用`viewLifecycleOwner`代替`this`
      • 2. 如果使用了`Fragment`的`this`,有可能产生的问题
  • 参考地址

MutableLiveData

  1. 粘性特性

    • 定义
      • MutableLiveData的粘性特性是指当一个观察者开始观察MutableLiveData时,如果数据已经有了一个值,那么这个观察者会立即收到这个已有值的通知。例如,假设MutableLiveData存储了一个用户的偏好设置(如主题颜色),在观察者(如一个Activity用于显示界面)开始观察之前,这个主题颜色的值可能已经被设置好了。当Activity开始观察该MutableLiveData时,它会立刻获取到这个已有的主题颜色值,就好像数据“粘”在了观察者上。
    • 实现原理
      • MutableLiveDataobserve()方法被调用时,它会检查当前是否已经有了一个非空的值。如果有,它会立即将这个值传递给新注册的观察者。在内部机制上,LiveDataMutableLiveData的父类)有一个版本号机制。每次数据更新时,版本号会增加。当观察者注册时,会比较观察者的初始版本号和LiveData的当前版本号,如果LiveData的版本号大于观察者的初始版本号,并且有数据值,就会将数据发送给观察者。
    • 应用场景
      • 这种特性在很多场景下都很有用。比如在应用启动时加载配置数据。如果MutableLiveData存储了应用的语言配置,当一个新的Activity启动并开始观察这个语言配置数据时,它可以立即获取到已有的语言配置,从而正确地设置界面语言,无需额外的操作来获取初始配置。
  2. 数据倒灌

    • 定义
      • 数据倒灌是指在某些情况下,当配置发生变化(如屏幕旋转)导致Activity或Fragment重建时,观察者可能会收到旧的数据。例如,一个Activity中有一个MutableLiveData存储用户输入的表单数据。当屏幕旋转时,Activity会被重建,新的观察者(重建后的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开发中,FragmentFragment中的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. 如果使用了Fragmentthis,有可能产生的问题

当在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来添加观察者,那么当它在FirstPageFragmentSecondPageFragment中被复用时,LiveData数据变化的通知会同时传递给两个页面中的观察者,导致它们的内部逻辑可能因为共享的数据处理而出现混乱,互相影响。

参考地址

关于LiveData粘性事件所带来问题的解决方案:https://www.jianshu.com/p/d0244c4c7cc9
简单粗暴解决LiveData『数据倒灌』的问题:https://blog.csdn.net/hewuzhao/article/details/117165379
豆包AI

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/16603.html

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

相关文章

系统架构设计师:软件架构的演化和维护

软件架构一般会经历初始设计、实际使用、修改完善和退化弃用的过程&#xff0c;其中修改完善的过程实际上就是软件架构的演化和维护过程&#xff0c;演化和维护的目的就是为了使软件能够适应环境的变化而进行的纠错性修改和完善性修改等。 软件架构的演化和维护过程是一个不断…

如何在 Ubuntu 上安装 Jupyter Notebook

本篇文章将教你在 Ubuntu 服务器上安装 Jupyter Notebook&#xff0c;并使用 Nginx 和 SSL 证书进行安全配置。 我将带你一步步在云服务器上搭建 Jupyter Notebook 服务器。Jupyter Notebook 在数据科学和机器学习领域被广泛用于交互式编码、可视化和实验。在远程服务器上运行…

IoT [remote electricity meter]

IoT [remote electricity meter] 物联网&#xff0c;远程抄表&#xff0c;电表数据&#xff0c;举个例子

使用ivew-ui-plus 的Submit组件踩坑 injection “LoginInstance“ not found 记录 问题原因分析与解决方案

问题描述: 在下面这个页面中 注册按钮使用了view-ui-plus的Submit组件 结果控制台报错 runtime-core.esm-bundler.js:257 Uncaught TypeError: Cannot read properties of undefined (reading handleSubmit)at Proxy.handleSubmit (viewuiplus.min.esm.js:32610:26)at callW…

力扣 LeetCode 1047. 删除字符串中的所有相邻重复项(Day5:栈与队列)

解题思路&#xff1a; 方法一&#xff1a;栈 class Solution {public String removeDuplicates(String s) {Deque<Character> stack new ArrayDeque<>();for (char c : s.toCharArray()) {if (stack.isEmpty() || stack.peek() ! c) stack.push(c);else stack.p…

无人机检测车辆——多目标检测

目录 YOLOv3&#xff08;You Only Look Once version 3&#xff09;简介 YOLOv3 的主要特点 YOLOv3 的结构 1. 特征提取网络&#xff08;Backbone&#xff09; 2. 检测头&#xff08;Head&#xff09; 3. 输出层 YOLOv3 损失函数 YOLOv3 的优势 YOLOv3 的应用 YOLOv3…

集群搭建高可用

contos7.9 部署3节点 hadoop3.4 高可用集群 contos7.9 部署3节点 hadoop3.4 高可用集群环境信息Hadoop与Zookeeper的版本对应关系服务器角色分配使用端口服务器配置配置免密登录服务器配置初始化 init_server.sh配置主机名映射所有节点配置 hosts文件 hadoop 安装环境配置下载安…

网络IP地址会经常换吗?深入解析与实操指南

在互联网的生态系统中&#xff0c;IP地址&#xff08;Internet Protocol Address&#xff09;是每台连接设备的唯一标识符&#xff0c;它在网络通信中起着至关重要的作用。然而&#xff0c;不少用户观察到自己的IP地址有时会发生变化&#xff0c;这引发了诸多疑问。本文旨在详细…

AI测试的主要研究方向介绍

随着AI技术的不断进步和应用场景的日益广泛&#xff0c;如何确保人工智能系统的可靠性和安全性&#xff0c; 变得日益重要。人工智能测试作为保障AI系统质量的关键环节&#xff0c;也随着AI技术不断向前发展。本文将介绍当前AI测试的主要研究方向&#xff0c;以期为大家提供一个…

Python3中str和bytes

参考文章&#xff1a;浅析Python3中的bytes和str类型 - Chown-Jane-Y - 博客园 Python 3最重要的新特性之一是对字符串和二进制数据流做了明确的区分。文本总是Unicode&#xff0c;由str类型表示&#xff0c;二进制数据则由bytes类型表示。Python 3不会以任意隐式的方式混用str…

比特币前景再度不明,剧烈波动性恐即将回归

比特币市场降温&#xff0c;波动性增加 自特朗普赢得美国总统大选以来&#xff0c;比特币市场的投机狂热有所降温&#xff0c;现货和衍生品市场的活跃度开始减弱。比特币在上周五跌破87000美元&#xff0c;较之前创下的历史高点低了约6500美元。这一变化受到美联储主席鲍威尔讲…

node对接ChatGpt的流式输出的配置

node对接ChatGpt的流式输出的配置 首先看一下效果 将数据用流的方式返回给客户端,这种技术需求在传统的管理项目中不多见,但是在媒体或者有实时消息等功能上就会用到,这个知识点对于前端还是很重要的。 即时你不写服务端,但是服务端如果给你这样的接口,你也得知道怎么去使用联…

esp32c3安装micropython环境

esp32c3竟然支持micropython环境&#xff0c;真的太让人高兴了。主要是python开发比较友好&#xff0c;开发速度要快于C和C&#xff0c; 可以用来快速创意验证。 下载 首先到官网&#xff1a;MicroPython - Python for microcontrollers 点击“download”进入下载页面&#…

Linux运维工程师推荐学习的开发语言

前言&#xff1a;会开发的运维和不会开发的运维可以说是两个世界的运维。 个人推荐python和go&#xff0c;前者可以做自动化运维&#xff0c;后者可以深挖k8s&#xff1b;最近就不先演示运维服务技术的部署和架构搭建了&#xff0c;在深挖自动化运维&#xff0c;为了让现在的工…

新手小白学习docker第八弹------实现MySQL主从复制搭建

目录 0 引言1 实操1.1 新建主服务器容器1.2 书写配置文件1.3 重启master实例1.4 进入mysql-master容器master容器实例内创建数据同步用户 1.5 新建从服务器容器1.6 书写配置文件1.7 重启slave实例1.8 查看主从同步状态1.9 进入mysql-slave容器1.9.1 配置主从复制1.9.2 查看主从…

我谈二值形态学基本运算——腐蚀、膨胀、开运算、闭运算

Gonzalez从集合角度定义膨胀和腐蚀&#xff0c;不易理解。 Through these definitions, you can interpret dilation and erosion as sliding neighborhood operations analogous to convolution (or spatial filtering). 禹晶、肖创柏、廖庆敏《数字图像处理&#xff08;面向…

力扣题目解析--合并两个链表

题目 将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 示例 1&#xff1a; 输入&#xff1a;l1 [1,2,4], l2 [1,3,4] 输出&#xff1a;[1,1,2,3,4,4]示例 2&#xff1a; 输入&#xff1a;l1 [], l2 [] 输出&#xff…

基于yolov8、yolov5的鸟类分类系统(含UI界面、训练好的模型、Python代码、数据集)

项目介绍 项目中所用到的算法模型和数据集等信息如下&#xff1a; 算法模型&#xff1a;     yolov8、yolov8 SE注意力机制 或 yolov5、yolov5 SE注意力机制 &#xff0c; 直接提供最少两个训练好的模型。模型十分重要&#xff0c;因为有些同学的电脑没有 GPU&#xff0…

css:浮动

网页的本质上就是摆放盒子&#xff0c;把盒子摆到相应的位置上 css提供了三种传统的布局方式&#xff1a; 普通流&#xff08;标准流&#xff09;&#xff1a;标签按默认方式排列&#xff0c;最基本的布局方式 浮动 定位 实际开发中&#xff0c;一个网页基本包含了三种这种布局…

Essential Cell Biology--Fifth Edition--Chapter one (6)

1.1.4.4 Internal Membranes Create Intracellular Compartments with Different Functions [细胞膜形成具有不同功能的细胞内隔室] 细胞核、线粒体和叶绿体并不是真核细胞中唯一的膜包围细胞器。细胞质中含有大量的[ a profusion of]其他细胞器&#xff0c;这些细胞器被单层膜…