LiveData源码分析,粘性事件,数据倒灌

在这里插入图片描述

最近面试天天被虐,有个问题问的很频繁,就是 LiveData 的数据倒灌问题怎么解决。

我不知道有多少人连数据倒灌是什么都没听过的,更不要说什么解决方案啦。

我按照我的理解描述一下数据倒灌:就是设置了 LiveData 的数据之后,再观察 LiveData,这时候拿到的数据是观察之前设置的数据,用比较难懂的说法就是之前设置的数据倒灌过来了。越说越乱了,其实就是一个粘性事件,不管你什么时候观察,都可以拿到最后设置的数据。

举个例子:我把接口返回的错误信息保存到一个 LiveData 中,在当前 Fragment 的 onViewCreated 中绑定,观察到错误信息后弹出了提示框,我关闭了提示框,跳到了另一个 Fragment,然后再返回刚才那个 Fragment,奇迹发生了,它又弹窗了!!!

先分析源码:

以下的代码都是部分关键代码,一些不想看的看不懂的代码我直接删掉了,,,

创建LiveData对象:

// 这样是正常写法吧
val liveData = MutableLiveData<String>()// 类:LiveData
// 这个是LiveData的构造方法
public LiveData() {// mData就是LiveData保存的最后一次更新的数据// private volatile Object mData;// static final Object NOT_SET = new Object();mData = NOT_SET;// 这个是LiveData的数据版本号,每一次更新数据版本号都会+1// private int mVersion;// static final int START_VERSION = -1;mVersion = START_VERSION;
}

观察LiveData对象:

// 是这样观察吧
liveData.observe(lifeCycleOwner) { println(it) 
}// 执行:liveData.observe(lifeCycleOwner) { println(it) }
// 类:LiveData
public void observe(LifecycleOwner owner, Observer<? super T> observer) {// 把生命周期和观察者绑定起来,构造方法在下面LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer);// 所有观察者保存到mObservers里面// private SafeIterableMap<Observer<? super T>, ObserverWrapper> mObservers = new SafeIterableMap<>();// 如果mObservers已经存在wrapper,则返回// 如果mObservers不存在wrapper的话,则put进去,返回null// 所以最终mObservers里面保存着所有的观察者ObserverWrapper existing = mObservers.putIfAbsent(observer, wrapper);// 我们这里只考虑第一次添加观察者的情况,直接绑定生命周期// 至此,观察者已经添加完毕owner.getLifecycle().addObserver(wrapper);
}// 执行:new LifecycleBoundObserver(owner, observer);
// 类:LifecycleBoundObserver
class LifecycleBoundObserver extends ObserverWrapper implements LifecycleEventObserver {@NonNullfinal LifecycleOwner mOwner;LifecycleBoundObserver(LifecycleOwner owner, Observer<? super T> observer) {super(observer);mOwner = owner;}
}// 执行:super(observer);
// 类:ObserverWrapper
private abstract class ObserverWrapper {final Observer<? super T> mObserver;ObserverWrapper(Observer<? super T> observer) {mObserver = observer;}
}

进入生命周期:

ObserverWrapper里面有一个mActive变量,如果没有进入生命周期,mActive默认是false的。
从人类角度思考的话,就是某个观察者,如果它绑定的生命周期没有进入到STARTED 状态的话,是不会激活的,没激活的话就不会观察到任何东西。
生命周期变化会回调onStateChanged 方法:

// 类:LifecycleBoundObserver(继承LifecycleEventObserver)
public void onStateChanged(@NonNull LifecycleOwner source,@NonNull Lifecycle.Event event) {// DESTROYED状态,移出观察者// 从这里可以看出LiveData不需要手动解除观察者,都是自动的Lifecycle.State currentState = mOwner.getLifecycle().getCurrentState();if (currentState == DESTROYED) {removeObserver(mObserver);return;}// 这里加了个prevState,确保在状态有变化之后才处理Lifecycle.State prevState = null;while (prevState != currentState) {prevState = currentState;// shouldBeActive(),至少是STARTED状态以上才会返回trueactiveStateChanged(shouldBeActive());currentState = mOwner.getLifecycle().getCurrentState();}
}// 执行:activeStateChanged(shouldBeActive());
// 类:ObserverWrapper
void activeStateChanged(boolean newActive) {// 我们只考虑正常情况,newActive=true// mActive默认为falseif (newActive == mActive) {return;}mActive = newActive;if (mActive) {// 分发,具体看setValue()部分,传入了具体的观察者// 这个代码是在ObserverWrapper里面的,这个this就是观察者,// 意思就是向这个观察者分发数据dispatchingValue(this);}
}

LiveData.setValue():

// kotlin:setValue()
liveData.value = "hello"// 类:LiveData
protected void setValue(T value) {// 每次setValue版本号都会+1,postValue最终也是setValuemVersion++;// 保存最后更新的数据mData = value;// 给观察者们分发数据// 没有传入具体的观察者,而是传入了null,表示给所有观察者分发dispatchingValue(null);
}// 执行:dispatchingValue(null);
// 类:LiveData
void dispatchingValue(@Nullable ObserverWrapper initiator) {if (mDispatchingValue) {mDispatchInvalidated = true;return;}mDispatchingValue = true;do {mDispatchInvalidated = false;if (initiator != null) {// 如果传入了具体的观察者,则直接调用considerNotify// 绑定观察者的时候会马上分发considerNotify(initiator);initiator = null;} else {// 没有传入具体的观察者,则遍历mObservers,拿到每一个观察者执行considerNotifyfor (Iterator<Map.Entry<Observer<? super T>, ObserverWrapper>> iterator =mObservers.iteratorWithAdditions(); iterator.hasNext(); ) {considerNotify(iterator.next().getValue());if (mDispatchInvalidated) {break;}}}} while (mDispatchInvalidated);mDispatchingValue = false;
}// 执行:considerNotify(iterator.next().getValue());
// 类:LiveData
private void considerNotify(ObserverWrapper observer) {if (!observer.mActive) {return;}// 我们只考虑分发的情况// mLastVersion默认是-1// 这里很重要,// 观察者每次收到数据后都会把自己的版本号设置成LiveData的版本号// 所以当观察者的版本号大于等于LiveData的版本号,// 那就说明这个观察者已经处理过这个版本的数据了if (observer.mLastVersion >= mVersion) {return;}// 每个观察者也会保存一份自己的版本号observer.mLastVersion = mVersion;// 至此回调用户定义的观察者,收工observer.mObserver.onChanged((T) mData);
}

LiveData.postValue():

// 使用:
liveData.postValue("hello")// 类:LiveData
protected void postValue(T value) {boolean postTask;synchronized (mDataLock) {// mPendingData默认是NOT_SET// static final Object NOT_SET = new Object();// volatile Object mPendingData = NOT_SET;postTask = mPendingData == NOT_SET;mPendingData = value;}if (!postTask) {return;}// 通过hanlder提交到主线程执行(所有需要主线程跑的代码全部都是通过handler提交的)// 很多JectPack的库都有用到这个ArchTaskExecutor// 我们自己的代码也可以直接用它,也可以给它设置我们自己的线程池ArchTaskExecutor.getInstance().postToMainThread(mPostValueRunnable);
}private final Runnable mPostValueRunnable = new Runnable() {public void run() {Object newValue;synchronized (mDataLock) {newValue = mPendingData;mPendingData = NOT_SET;}// 这里回到setValue()的情况setValue((T) newValue);}
};

粘性事件和数据倒灌

从上面分析可以看到LiveData里面保存的上一次分发的数据mData,这是一个Object对象,并且在分发完毕后不会置空,所以后来的观察者也能观察到这个对象。

解决思路:

不要解决!!!

LiveData 本来就是这么设计的,是用来保存数据的,不是用来分发事件的。

解决思路参考:

1、反射。我看很多博客都说可以用反射,在 observe 的时候反射拿到 LiveData 的版本号,再反射赋值给 Observer 的版本号,但是,从理论上分析我就觉得不可能。首先 LifecycleBoundObserver 是在 observe 方法中生成的,通过反射根本拿不到这个对象。但是可以从 mObservers 中拿到,然而是在 super.observe(owner, observer) 之后才能拿到,这时候已经绑定生命周期并且触发分发了,拿到 Observer 还有什么意义。

2、https://github.com/KunMinX/UnPeek-LiveData 这个库看着可行。大概原理就是,自己定义一个 MyLiveData 和 MyObserver,然后自己维护一份 MyLiveData 和 MyObserver 的版本号,在创建 MyObserver 的时候把 MyObserver 的版本号设置成 MyLiveData 的版本号,在 MyObserver 的 onChanged 方法中判断 MyLiveData 的版本号大于等于 MyObserver 的版本号才执行。和前面反射的原理其实是差不多的,只是不反射了,而是自己维护一套版本号。

3、其他的都不用考虑了。

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

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

相关文章

论文阅读:Distributed Initialization for VVIRO with Position-Unknown UWB Network

前言 Distributed Initialization for Visual-Inertial-Ranging Odometry with Position-Unknown UWB Network这篇论文是发表在ICRA 2023上的一篇文章&#xff0c;本文提出了一种基于位置未知UWB网络的一致性视觉惯性紧耦合优化测距算法( DC-VIRO )的分布式初始化方法。 对于…

Vue学习笔记-Vuex四个map方法的使用

在组件中导入方法 import {mapState,mapGetters,mapActions,mapMutations} from vuexmapState方法的使用 注意&#xff1a;这里的…是ES6语法&#xff0c;表示将该的对象所有属性展开&#xff0c;放入其所在上级对象中 ...... computed:{//借助mapState生成计算属性&#xf…

处理跨域问题

这里只讨论后端对跨域支持,前端的跨域支持一般都是在测试阶段用用的,跨域还是要后端解决 跨域问题的产生:浏览器的一种安全机制-->同源策略限制 同源策略:URL中包括协议&#xff0c;域名&#xff0c;IP&#xff0c;端口都要完全相同&#xff0c;如果有一项不同&#xff0c;浏…

《荒野大镖客》游戏提示emp.dll丢失怎么搞,总结五个修复教程分享

在玩荒野大镖客这款游戏时&#xff0c;有些玩家可能会遇到找不到emp.dll文件的问题。这个问题通常会导致游戏无法正常运行或出现错误提示。本文将介绍荒野大镖客找不到emp.dll丢失的6种解决方法&#xff0c;并解释emp.dll是什么以及导致其丢失的原因。 什么是emp.dll&#xff…

2021-07-31

单日3亿日志数据准实时存储和分析 –ClickHouse 在自如大前端研发中心的应用 第一章 架构设计 和 用户体系建设 文章目录 单日3亿日志数据准实时存储和分析前言一、pandas是什么&#xff1f;二、使用步骤1.引入库2.读入数据 总结 前言 用户行为数据的收集和分析&#xff0c;…

JavaScript 的初步学习下篇

函数 语法格式 创建函数/函数声明/函数定义 function 函数名(形参列表) {函数体return 返回值; }函数调用 函数名(实参列表) // 不考虑返回值 返回值 函数名(实参列表) // 考虑返回值 注: 函数定义并不会执行函数体内容, 必须要调用才会执行. 调用几次就会执行几次. js 中…

怎么样的软件测试工程师才算“大神”?

&#x1f4e2;专注于分享软件测试干货内容&#xff0c;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; 如有错误敬请指正&#xff01;&#x1f4e2;交流讨论&#xff1a;欢迎加入我们一起学习&#xff01;&#x1f4e2;资源分享&#xff1a;耗时200小时精选的「软件测试」资…

acwing算法基础之数学知识--Nim游戏和集合Nim游戏

目录 1 基础知识2 模板3 工程化 1 基础知识 &#xff08;一&#xff09; Nim游戏&#xff1a; n n n堆物品&#xff0c;每堆有 a i a_i ai​个&#xff0c;两个玩家轮流取走任意一堆的任意个物品&#xff0c;但不能不取。取走最后一个物品的人获胜。 结论&#xff1a;如果这n…

Java 多线程之 ReentrantReadWriteLock(读写锁/共享锁/排他锁)

文章目录 一、概述二、使用方法三、测试示例四、完整示例 一、概述 ReentrantReadWriteLock 是Java中提供的一种读写锁实现&#xff0c;它允许多个线程同时读取共享资源&#xff0c;但在写操作时需要独占访问。它是对传统互斥锁的一种改进&#xff0c;可以提高并发性能。 读写…

C#开发的OpenRA游戏之属性SelectionDecorations(13)

C#开发的OpenRA游戏之属性SelectionDecorations(13) 在前面分析SelectionDecorations属性类时,会发现它有下面这个属性: public class SelectionDecorations : SelectionDecorationsBase, IRender { readonly Interactable interactable; 它是定义了一个Interactabl…

【编写UI自动化测试集】Appium+Python+Unittest+HTMLRunner​

简介 获取AppPackage和AppActivity 定位UI控件的工具 脚本结构 PageObject分层管理 HTMLTestRunner生成测试报告 启动appium server服务 以python文件模式执行脚本生成测试报告 下载与安装 下载需要自动化测试的App并安装到手机 获取AppPackage和AppActivity 方法一 有源码…

怎么安装php扩展

在Windows环境下安装PHP扩展&#xff1a; 下载扩展&#xff1a; 对于一些常见的扩展&#xff0c;可以在官方的PHP网站上找到预编译的DLL文件。前往PECL或者PHP官网来下载相应版本的DLL文件。 更新php.ini配置文件&#xff1a; 找到PHP的安装目录&#xff0c;并且打开php.ini文…

大杀四方,华为组建智能车大联盟 | 百能云芯

最近&#xff0c;华为和一系列汽车公司合资的新公司迎来新的进展。除了与长安汽车的合作外&#xff0c;据传华为已经邀请奇瑞、赛力斯、北汽以及江淮汽车入股新公司&#xff0c;这将使华为成为中国智能汽车平台的重要主导者。 根据澎湃新闻的报道&#xff0c;知情人透露&#x…

Java EE 多线程

文章目录 1. 认识线程1.1 什么是进程1.2 什么是线程1.2.1. 线程是怎么做到的呢&#xff1f;1.2.2. 进程和线程的关系 1.3 多线程编程1.3.1. 第一个多线程程序1.3.2. 使用 jconsole 命令查看线程1.3.3. 实现 Runnable 接口&#xff0c;重写 run1.3.4. 继承 Thread 重写 run&…

2023-11-29 Linux环境创建命名管道小实验

点击 <C 语言编程核心突破> 快速C语言入门 Linux环境创建命名管道小实验 前言一、mkfifo()函数AI助手提供协作二、代码总结 前言 要解决问题: Linux环境用mkfifo()创建个管道, 但是被阻塞, 发现是管道虽然建立了, 但是没有进程打开它, 以至于阻塞. 想到的思路: 开始时…

悲观锁乐观锁在django中使用

1 悲观锁乐观锁 1.1 并发控制 1.1 悲观锁 1.2 乐观锁 1.3 悲观锁乐观锁使用场景 2 django中开启事务 2.1 全局开启事务 2.2 视图开启事务 2.3 局部使用事务 2.4 savepoint回滚 2.5 事务提交后回调函数 3 django中使用悲观锁 3.1 前置条件&#xff0c;表模型 3.1 模拟秒杀生成…

配电网重构单时段+多时段(附带matlab代码)

配电网重构单时段多时段 对于《主动配电网最优潮流研究及其应用实例》的基本复现 简介&#xff1a;最优潮流研究在配电网规划运行中不可或缺&#xff0c;且在大量分布式能源接入的主动配电网环境下尤为重要。传统的启发式算法在全局最优解和求解速度上均无法满足主动配电网运行…

八股文-如何理解Java中的多态

什么是多态&#xff1f; 多态是面向对象编程的一个重要概念&#xff0c;它允许一个对象以不同的形式表现。也就是说&#xff0c;在父类中定义的属性和方法&#xff0c;在子类继承后&#xff0c;可以有不同的数据类型或表现出不同的行为。这可以使得同一个属性或方法&#xff0…

读书笔记:彼得·德鲁克《认识管理》第34章 结构与战略

一、章节内容概述 在现代管理的短暂历史上&#xff0c;我们曾经两次相信找到了正确答案。一 次是第一次世界大战时期亨利法约尔构建的“职能制组织”&#xff0c;另一次是 一代人时间后阿尔弗雷德斯隆构建的“联邦分权制组织”。在适当的领 域和条件下&#xff0c;这两种组织结…

操作系统 day14(进程同步、进程互斥)

进程同步 概念 进程的异步性体现在&#xff0c;例如&#xff1a;当有I/O操作时&#xff0c;进程需要等待I/O操作&#xff0c;而每个I/O操作又是不同的&#xff0c;所以进程没有一个固定的顺序&#xff0c;固定的时间来执行&#xff0c;而这体现了进程的异步性。 进程互斥 …