【用法总结】LiveData组件要点

【用法总结】LiveData组件要点

      • 1、如何实现和生命周期的关联?
        • 1.1 observe的实现逻辑:
        • 1.2 观察者的装饰者:ObserverWrapper
        • 1.3 观察者集合的存储:SafeIterableMap<Observer<? super T>, ObserverWrapper>,以obser为key,ObserverWrapper对value
      • 2、onChange()执行时机
      • 3、说LiveData会数据倒灌是这么回事?
        • 3.1 本质
        • 3.2 使用LiveData实现事件注册-分发逻辑的问题
        • 3.3 短时间内连续执行多次postValue,只有最后一次更新的值会收到数据变化的onChanged回调
      • 参考文章

1、如何实现和生命周期的关联?

调用observe()方法时,第一个参数传入LifecycleOwner对象,而LifecycleOwner能通过getLifecycle()方法获取到lifecycle对象,然后执行lifecycle.addObserver()添加LiveData中数据(mData)变化的观察者对象。

1.1 observe的实现逻辑:
    @MainThreadpublic void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer) {if (owner.getLifecycle().getCurrentState() == DESTROYED) {return;}LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer);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);}

LifecycleOwner-ViewModel-LiveData的层级结构
如上图所示,是使用LiveData组件实现数据更新-订阅的开发模式的层级结构。

  • mData:setValue()之后更新的数据
  • mVersion:在每一次调用setValue()时会进行更新
  • mPending:调用postValue()时先将新的值暂存到该变量中,然后将Runnable任务post()到Handler中后,再将mPendingData
    中的值赋值给mData变量,然后就是走setValue()的流程了。
1.2 观察者的装饰者:ObserverWrapper

    该类是Observer的包装类,是一个抽象类,具体实现类是LifecycleBoundObserver,内部包裹了mOwner、mObserver、mActive、mLastVersion变量,如下:

  • ObserverWrapper抽象类
private abstract class ObserverWrapper {final Observer<? super T> mObserver;boolean mActive;// 初始值是-1,LiveData的初始值是0,那么第一次对比时// observer.mLastVersion肯定是小于mVersion的,一定会更新一次值int mLastVersion = START_VERSION;
}
  • ObserverWrapper的实现类LifecycleBoundObserver
        在ObserverWrapper包装mLastVersion和mObserver的基础上,把LifecycleOwner(中文咋说?生命周期所有者嘛?这不重要,方正不外乎Activty、Fragment、或者自己实现的自定义生命周期组件)也包装了进来,
    主要是为了拿到当前Activity/Fragment的生命周期状态,做一些逻辑,比如:onDestroy时要removeObserver,判断当前是否时活跃状态,也就是isAtLeast(STARTED)状态,我们在编写业务代码时也经常传递这个参数给到子模块,完成对于生命周期组件的状态判断、添加、移除生命周期监听的observer对象等逻辑。
class LifecycleBoundObserver extends ObserverWrapper implements GenericLifecycleObserver {@NonNull final LifecycleOwner mOwner;// 将LifecycleOwner对象包装到其中,用户实现一些生命周期的逻辑,// 也能直接拿到lifecycle对象,其实现就是Activity、Fragment。LifecycleBoundObserver(@NonNull LifecycleOwner owner, Observer<? super T> observer) {super(observer);mOwner = owner;}@Overrideboolean shouldBeActive() {return mOwner.getLifecycle().getCurrentState().isAtLeast(STARTED);}@Overridepublic void onStateChanged(LifecycleOwner source, Lifecycle.Event event) {if (mOwner.getLifecycle().getCurrentState() == DESTROYED) {removeObserver(mObserver);return;}activeStateChanged(shouldBeActive());}@Overrideboolean isAttachedTo(LifecycleOwner owner) {return mOwner == owner;}@Overridevoid detachObserver() {mOwner.getLifecycle().removeObserver(this);}}
1.3 观察者集合的存储:SafeIterableMap<Observer<? super T>, ObserverWrapper>,以obser为key,ObserverWrapper对value

    这是在androidx.core.common库中定义的一个map结构,仔细看下其实是一个链表实现的,实现的是迭代器接口。
在这里插入图片描述

private SafeIterableMap<Observer<? super T>, ObserverWrapper> mObservers =new SafeIterableMap<>();
  • 疑问:为什么不是使用HashMap、ArrayMap等现有集合,非要自己实现一个映射表?炫技嘛?如果被面试问到你能搭粗来嘛?可以把答案打到评论区!!

2、onChange()执行时机

  • (1)onStart()之后调用setValue()立即回调onChanged(newData)

  • (2)调用observe()时,如果Observer是新建的实例,那么其绑定的mLastVersion初始值是-1,当调用lifecycleOwner.addObserver()时会调用activeStateChanged(), 然后触发dispatchingValue(), 因为不满足observe.mLastVersion>=mVersion(默认值是0,每次setValue/postValue加1),然后回调onChanged(newData)
    如果是在onStop()中注册observer,那么会在回到onStart()后会回调一次onChanged()
    ⚠️ LiveData中判断LifecycleOwner是判断生命周期状态是否是isAtLeast(START),所以onPause时调用observe()方法也是会回调onChanged()方法的。

  • (3)LifecycleOwner生命周期变化时,还存在同因生命周期<Lifecycle.State.ON_START并没有回调onChanged(newData),则会回调一次onChanged(),把最新的mData值回调给onChanged()方法

3、说LiveData会数据倒灌是这么回事?

3.1 本质

    LiveData其实本质上实现的是将事件发送时机限定在LifecycleOwner的生命周期内的粘性事件。LiveData在生命周期可见时,将不可见时更新的mData版本回传到onChanged()中,当setValue()是在observe()之前调用的,那调用observe()时会把前面setValue的最新的值传给观察者的onChanged()。这个被观察者的变量是否有更新过的逻辑,主要靠LiveData类中定义的mVersion和ObserverWrapper的mLastVersion对比逻辑来实现的。

  • 分发更新后的值给观察者是调用dispatchingValue方法实现的,分别在setValue和onActiveStateChanged时调用,
    setValue调用分发value的逻辑很好理解,onActiveStateChanged方法在调用observe()方法增加新观察者时也会调用,然后根据版本决定是否调用observer的onChangd方法。
    @SuppressWarnings("unchecked")private void considerNotify(ObserverWrapper observer) {if (!observer.mActive) {return;}// Check latest state b4 dispatch. Maybe it changed state but we didn't get the event yet.//// we still first check observer.active to keep it as the entrance for events. So even if// the observer moved to an active state, if we've not received that event, we better not// notify for a more predictable notification order.if (!observer.shouldBeActive()) {observer.activeStateChanged(false);return;}if (observer.mLastVersion >= mVersion) {return;}observer.mLastVersion = mVersion;observer.mObserver.onChanged((T) mData);}

    不使用LiveData进行数据更新时,一般是在UI控件变量准备好了,然后获取数据,再把数据传递给UI控件变量的某个属性,实现UI的更新,这种命令式UI的开发模式本身没有什么问题,符合日常的开发逻辑。
    但是因为Android的UI架构都是基于Activity/Fragment生命周期管理的,就必然存在数据异步获取到时界面已经处于不活跃状态的情况。甚至很可能因为内存不足已经销毁了,然后在用户操作下回到活跃状态了,那么异步拿到的数据可能是没有塞给UI控件的,一般都是在onResume时又异步获取一次数据,然后更新到UI控件上。

3.2 使用LiveData实现事件注册-分发逻辑的问题

    基于3.1对于LiveData数据更新本质的分析,如果我们使用LiveData的setValue/postValue,然后通过observe()分发进行监听,使用String/Int/CustomEvent(自定义的事件类,类似于EventBus)等数据做为事件分发。虽然这种方式能够避免内存泄漏,但是事件是粘性的,先发送事件,然后注册也会接收到该事件,这样的逻辑实际上并不是我们日常业务的事件逻辑。

    google官方sample只处理一次的LiveData事件实现方案:
官方todoapp的Event实现
    ps: 不得不说,老外的文章写得还是蛮清楚的,循序渐进,解释各种方案的问题,层层递进,对读者更好的理解有莫大帮助。

3.3 短时间内连续执行多次postValue,只有最后一次更新的值会收到数据变化的onChanged回调

    这个问题是因为postValue的实现逻辑导致的,如下是postValue的代码:

protected void postValue(T value) {boolean postTask;synchronized (mDataLock) {postTask = mPendingData == NOT_SET;mPendingData = value;}if (!postTask) {return;}ArchTaskExecutor.getInstance().postToMainThread(mPostValueRunnable);
}private final Runnable mPostValueRunnable = new Runnable() {@SuppressWarnings("unchecked")@Overridepublic void run() {Object newValue;synchronized (mDataLock) {newValue = mPendingData;mPendingData = NOT_SET;}setValue((T) newValue);}
};

    第一次postValue,postTask是true,mPendingData赋值为value,首次调用因为mPending值是NOT_SET,所以postTask为true, 会调用postToMainThread方法。
    然后同步短时间内再次调用postValue,这时候因为mPostValueRunnable中的逻辑还未执行,所以mPendingData会赋值为新的value值,但是这次因为mPending不再是NOT_SET,而是第一次调用的值了,所以postTask为false,postValue方法直接return了。
    然后在mPostValueRunnable.run方法被执行到时,mPendingData已经是最后一次调用postValue时传入的值了,所以mPostValueRunnable中调用的setValue方法,最终回调到Observer#onChanged的是最后一次postValue传入的值。

  • 同时,官方对postValue的备注也有提醒,如下所示:
    在这里插入图片描述
  • 可见,官方文档的描述更严谨,是说主线程的mPostValueRunnable被执行之前,多次调用postValue传入的值,最后只会传给观察者最后一次的值。
    在这里插入图片描述
  • 实际写一个例子说明这个现象:
// 按钮点击是,连续多次执行postValue
simpleViewModel.liveData1.postValue("我是新数据0")
simpleViewModel.liveData1.postValue("我是新数据1")
simpleViewModel.liveData1.postValue("我是新数据2")
simpleViewModel.liveData1.postValue("我是新数据3")
// onChanged回调的结果只有最后一次
23:56:22.732  D  liveData1: onChanged newValue = 我是新数据3// 如果给两次postValue之间加间隔呢?
simpleViewModel.viewModelScope.launch(Dispatchers.IO) {simpleViewModel.liveData1.postValue("我是新数据0")delay(10)simpleViewModel.liveData1.postValue("我是新数据1")delay(10)simpleViewModel.liveData1.postValue("我是新数据2")delay(10)simpleViewModel.liveData1.postValue("我是新数据3")
}
// 输出结果如下,就没有出现丢失数据的更新的情况了。并且尝试把间隔时间改成1ms,多次操作会出现可能丢失部分数据,
// 可能全部不丢失的,这里能说明上面关于postValue短时间内连续更新数据,可能只有最后一次分发给观察者的原因了。
// !!这里也能说明,使用LiveData实现事件分发,要也别注意异步分发事件可能丢事件的。
// 这里也说明了LiveData在异步数据流上是存在缺陷的,当然google又出了Flow组件专门用于处理数据流,待后续会分享其用法。
00:03:29.143  D  liveData1: onChanged newValue = 我是新数据0
00:03:29.199  D  liveData1: onChanged newValue = 我是新数据1
00:03:29.212  D  liveData1: onChanged newValue = 我是新数据2
00:03:29.232  D  liveData1: onChanged newValue = 我是新数据3
  • 截止目前LiveData已经有两个需要我们特别关注,可能彩坑的点了:
    (1)数据粘性更新;
    (2)异步更新数据的postValue可能丢失更新过程中的值,不适用于异步数据流的更新&展示。
    比如:要实现进度的展示,用LiveData#postValue就可能出现进度变化过程丢一部分了,UI要的一些变化过程没了(貌似这个举例不妥~),作为事件总线分发事件可能丢失等情况;

参考文章

【1】LiveData with SnackBar, Navigation and other events (the SingleLiveEvent case)

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

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

相关文章

Linux第30步_通过USB OTG将固件烧写到eMMC中

学习目的&#xff1a;在Win11中&#xff0c;使用STM32CubeProgrammer工具&#xff0c;通过USB OTG将固件烧写到eMMC中。 安装软件检查&#xff1a; 1、是否安装了JAVA; 2、是否安装了STM32CubeProgrammer工具; 3、是否安装 了DFU驱动程序; 4、是否安装了“Notepad”软件; …

机器学习之卷积神经网络

卷积神经网络是一类包含卷积计算且具有深度结构的前馈神经网络,是深度学习的代表算法之一。卷积神经网络具有表征学习能力,能够按其阶层结构对输入信息进行平移不变分类,因此又称为SIANN。卷积神经网络仿照生物的视知觉机制构建,可以进行监督学习和非监督学习,其隐含层内的…

Verilog刷题笔记16

题目&#xff1a; Since digital circuits are composed of logic gates connected with wires, any circuit can be expressed as some combination of modules and assign statements. However, sometimes this is not the most convenient way to describe the circuit. Pro…

嵌入式软件工程师面试题——2025校招社招通用(二十一)

说明&#xff1a; 面试群&#xff0c;群号&#xff1a; 228447240面试题来源于网络书籍&#xff0c;公司题目以及博主原创或修改&#xff08;题目大部分来源于各种公司&#xff09;&#xff1b;文中很多题目&#xff0c;或许大家直接编译器写完&#xff0c;1分钟就出结果了。但…

软件开发架构

【 一 】软件开发架构图 【 1】ATM和选课系统 三层的开发架构 前段展示台 后端逻辑层 数据处理层 【二】软件开发架构的步骤流程 需求分析&#xff1a;在软件开发架构设计之前&#xff0c;需要对应用系统进行需求分析&#xff0c;明确用户需求、功能模块、业务流程等内容。…

CSS Day10

10.1 2D位移 属性名&#xff1a;transform 属性值&#xff1a;translateX 水平方向的位移 相对于自身位置移动 translateY 垂直方向的位移 相对于自身位置移动 transform&#xff1a;translate(x,y); 位移和定位搭配使用&#xff1a; position:absolute; top:50%; left:50%; tr…

基于springboot+vue的校园管理系统(前后端分离)

博主主页&#xff1a;猫头鹰源码 博主简介&#xff1a;Java领域优质创作者、CSDN博客专家、公司架构师、全网粉丝5万、专注Java技术领域和毕业设计项目实战 主要内容&#xff1a;毕业设计(Javaweb项目|小程序等)、简历模板、学习资料、面试题库、技术咨询 文末联系获取 项目背景…

npm install 无反应 npm run serve 无反应

说明情况&#xff1a;其实最开始我就是发现我跟着黑马的苍穹外卖的前端day2的环境搭建做的时候&#xff0c;到这一步出现了问题&#xff0c;无论我怎么 npm install 和 npm run serve 都没有像黑马一样有很多东西进行加载&#xff0c;因此我换了一种方法 1.在这个文件夹下cmd …

2024年1月【ORACLE战报】| 新年第一波OCP证书来了!

相关文章&#xff1a; 2023年12月【考试战报】|ORACLE OCP 19C考试通过 2023年10月【考试战报】|ORACLE OCP 19C考试通过 2023.7月最新OCP考试通过|微思-ORACLE官方授权中心 OCP 19C题库稳定&#xff01;https://download.csdn.net/download/XMWS_IT/88309681?ops_request_…

【昕宝爸爸小模块】深入浅出之POI是如何做大文件的写入的

➡️博客首页 https://blog.csdn.net/Java_Yangxiaoyuan 欢迎优秀的你&#x1f44d;点赞、&#x1f5c2;️收藏、加❤️关注哦。 本文章CSDN首发&#xff0c;欢迎转载&#xff0c;要注明出处哦&#xff01; 先感谢优秀的你能认真的看完本文&…

酷狗音乐逆向(js逆向)

免责声明&#xff1a;     本篇博文的初衷是分享自己学习逆向分析时的个人感悟&#xff0c;所涉及的内容仅供学习、交流&#xff0c;请勿将其用于非法用途&#xff01;&#xff01;&#xff01;任何由此引发的法律纠纷均与作者本人无关&#xff0c;请自行负责&#xff01;&…

使用easyexcel 导出多级表头demo

先看效果&#xff1a; 1、引入maven依赖 <!--EasyExcel --> <dependency><groupId>com.alibaba</groupId><artifactId>easyexcel</artifactId><version>3.2.1</version> </dependency> 2、实体类 package com.…

Spring框架面试题

目录 1.Spring中bean的生命周期 2.Spring中bean的循环依赖 3.SpringMVC执行流程 4.Springboot自动装配原理 5.Spring框架常见注解(Spring、Springboot、SpringMVC) 6.mybatis执行流程 7.mybatis延迟加载使用及原理 8.mybatis一级、二级缓存 1.Spring中bean的生命周期 2.…

Unity向量叉乘

叉乘计算公式 Unity中叉乘计算 Vector3.Cross(A.position, B.position); 几何意义 假设向量A和B 都在XZ平面上 向量A叉乘向量B y大于0 证明 B在A右侧 y小于0 证明 B在A左侧 示例 Vector3 C Vector3.Cross(A.position, B.position); if(C.y > 0) {print("B在A右侧&qu…

rust跟我学六:虚拟机检测

图为RUST吉祥物 大家好,我是get_local_info作者带剑书生,这里用一篇文章讲解get_local_info是怎么检测是否在虚拟机里运行的。 首先,先要了解get_local_info是什么? get_local_info是一个获取linux系统信息的rust三方库,并提供一些常用功能,目前版本0.2.4。详细介绍地址:…

网络攻防和CTF有什么区别和关系?

网络攻防和CTF&#xff08;Capture The Flag&#xff09;之间存在着密切的联系和区别。在理解它们的关系之前&#xff0c;我们需要先了解每个概念的含义和特点。 网络攻防是一种针对网络系统的攻击和防御技术&#xff0c;主要涉及黑客攻击和安全防护两个方面。攻击方会利用各…

基于改进凸优化算法的多机编队突防航迹规划

源自&#xff1a;系统工程与电子技术 作者&#xff1a;刘玉杰, 李樾, 韩维, 崔凯凯 “人工智能技术与咨询” 摘要 为更好地发挥多机编队在低空突防作战中的优势, 对已有的凸优化算法进行改进, 提出一种多机编队低空突防航迹规划方法。首先, 根据低空突防任务特点进行问题建…

课表排课小程序怎么制作?多少钱?

在当今的数字化时代&#xff0c;无论是购物、支付、点餐&#xff0c;还是工作、学习&#xff0c;都离不开各种各样的微信小程序。其中&#xff0c;课表排课小程序就是许多教育机构和学校必不可少的工具。那么课表排课小程序怎么制作呢&#xff1f;又需要多少钱呢&#xff1f; …

electron+vue项目使用serialport报错Cannot read property ‘indexOf‘ of undefined解决办法

描述 使用ElectronVue项目时引入serialport串口后启动时报下面错误 Cannot read property indexOf of undefined解决方法 打开vue.config.js找到pluginOptions -> electronBuilder -> externals添加serialport module.exports {pluginOptions: {electronBuilder: {e…

MBTI+大模型=甜甜的恋爱?美国新年AI裁员潮;中国大模型人才分布图;20分钟览尽NLP百年;Transformer新手入门教程 | ShowMeAI日报

&#x1f440;日报&周刊合集 | &#x1f3a1;生产力工具与行业应用大全 | &#x1f9e1; 点赞关注评论拜托啦&#xff01; &#x1f251; GenAI 是美国「2024 年裁员潮」罪魁祸首吗&#xff1f;来看几组数据 https://www.trueup.io/layoffs 补充一份背景&#xff1a;&#…