【用法总结】LiveData组件要点

news2025/5/23 4:10:39

【用法总结】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的实现逻辑:
    @MainThread
    public 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;
        }

        @Override
        boolean shouldBeActive() {
            return mOwner.getLifecycle().getCurrentState().isAtLeast(STARTED);
        }

        @Override
        public void onStateChanged(LifecycleOwner source, Lifecycle.Event event) {
            if (mOwner.getLifecycle().getCurrentState() == DESTROYED) {
                removeObserver(mObserver);
                return;
            }
            activeStateChanged(shouldBeActive());
        }

        @Override
        boolean isAttachedTo(LifecycleOwner owner) {
            return mOwner == owner;
        }

        @Override
        void 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")
    @Override
    public 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.coloradmin.cn/o/1393124.html

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

相关文章

ChatGPT 商业提示词攻略书

原文&#xff1a;ChatGPT Business Prompt Playbook 译者&#xff1a;飞龙 协议&#xff1a;CC BY-NC-SA 4.0 一、书系介绍 人工智能发展迅速。非常迅速。 所以我希望你做两件事&#xff1a; (1) 在 Twitter 上关注我&#xff1a;iamkylebalmer (2) 订阅我的免费电子邮件通…

springcloud Client端cloud-consumer-order80

文章目录 简介建立module修改pom修改yml主启动类把公共代码写在一个mudule 里面测试 简介 这个是和之前的8001相互配合端口测试 这里的80的用户测试端口。 代码在&#xff1a;GitHub 上&#xff1a;https://github.com/13thm/study_springcloud/tree/main/days2 建立module …

小程序 自定义组件和生命周期

文章目录 ⾃定义组件创建⾃定义组件声明组件编辑组件注册组件 声明引⼊⾃定义组件⻚⾯中使⽤⾃定义组件定义段与⽰例⽅法组件-⾃定义组件传参过程 小程序生命周期应用生命周期页面生命周期页面生命周期 ⾃定义组件 类似vue或者react中的自定义组件 ⼩程序允许我们使⽤⾃定义组件…

1.C语言基础知识

这里写目录标题 1.第一个C语言程序2.注释3.标识符4.关键字5.数据类型6.变量7.常量8.运算符9.输入输出输入输出 1.第一个C语言程序 C语言的编程框架 #include <stdio.h> int main() {/* 我的第一个 C 程序 */printf("Hello, World! \n");return 0; }2.注释 单行…

RHCE9学习指南 第21章 用bash写脚本

grep的用法是&#xff1a; grep 关键字 file 意思是从file中过滤出含有关键字的行。 例如&#xff0c;grep root /var/log/messages&#xff0c;意思是从/var/log/messages中过滤出含有root的行。这里很明确的是过滤含有“root”的行。 如果我要是想在/var/log/messages中过滤…

『 C++ 』红黑树RBTree详解 ( 万字 )

文章目录 &#x1f996; 红黑树概念&#x1f996; 红黑树节点的定义&#x1f996; 红黑树的插入&#x1f996; 数据插入后的调整&#x1f995; 情况一:ucnle存在且为红&#x1f995; 情况二:uncle不存在或uncle存在且为黑&#x1f995; 插入函数代码段(参考)&#x1f995; 旋转…

阿里云服务器4核8G配置最新优惠价格表(2024活动报价)

阿里云服务器4核8g配置云服务器u1价格是955.58元一年&#xff0c;4核8G配置还可以选择ECS计算型c7实例、计算型c8i实例、计算平衡增强型c6e、ECS经济型e实例、AMD计算型c8a等机型等ECS实例规格&#xff0c;规格不同性能不同&#xff0c;价格也不同&#xff0c;阿里云服务器网al…

Smart Tomcat

Smart Tomcat插件可以让idea图形化界面让代码部署到tomcat上达成一键打包部署的过程 下面是idea安装使用Smart Tomcat的过程 我们直接在plugins(插件)里搜索Tomcat 然后下载第一个 然后点击Apply(应用) 在一个项目中 第一次使用时要进行配置Smart Tomcat Name 可以不配置…

npm超详细安装(包括配置环境变量)!!!npm安装教程(node.js安装教程)

安装node.js:(建议选择相对低一点的版本&#xff0c;相对稳定)​下载完成直接点击next即可(安装过程中会直接添加path的系统变量&#xff0c;变量值是自己的安装路径&#xff0c;可自行选择&#xff0c;比如&#xff1a;D:\software\)​安装完成:winR打开电脑控制台&#xff0c…

【每周AI简讯】GPT-5将有指数级提升,GPT Store正式上线

AI7 - Chat中文版最强人工智能 OpenAI的CEO奥特曼表示GPT-5将有指数级提升 GPT奥特曼参加Y-Combinator W24启动会上表示&#xff0c;我们已经非常接近AGI。GPT-5将具有更好的推理能力、更高的准确性和视频支持。 GPT Store正式上线 OpenAI正式推出GPT store&#xff0c;目前…

20240117-【UNITY 学习】增加墙跑功能和跳墙功能

替换脚本PlayerCam_01.cs using System.Collections; using System.Collections.Generic; using UnityEngine; using DG.Tweening;public class PlayerCam_02 : MonoBehaviour {// 视觉灵敏度参数public float sensX 400;public float sensY 400;// 视角垂直旋转角度限制publ…

平衡操控应用场景探讨及RTSP技术实现

平衡操控应用背景 平行操控场景&#xff0c;通过超低延时视频通信技术与实时信令技术结合&#xff0c;使得操作者可以实时的驾驶/操作远端的无人车或机械设备。 相比传统近距离的遥控&#xff0c;平行操控的传输网构建在互联网之上&#xff0c;突破了传统距离限制&#xff0c…

防火墙如何处理nat(私网用户访问Internet场景)

目录 私网用户访问Internet场景源NAT的两种转换方式NAT No-PAT NAPT配置思路规划 NAPT配置命令配置接口IP地址并将接口加入相应安全区域配置安全策略配置NAT地址池配置源NAT策略配置缺省路由配置黑洞路由 私网用户访问Internet场景 多个用户共享少量公网地址访问Internet的时候…

go语言(三)----函数

1、函数单变量返回 package mainimport "fmt"func fool(a string,b int) int {fmt.Println("a ",a)fmt.Println("b ",b)c : 100return c}func main() {c : fool("abc",555)fmt.Println("c ",c)}2、函数多变量返回 pack…

《Linux C编程实战》笔记:出错处理

这一节书上把它放到线程这一章&#xff0c;按理说应该在前面就讲了 头文件errno.h定义了变量errno&#xff0c;它存储了错误发生时的错误码&#xff0c;通过错误码可以得到错误的信息 程序开始执行时&#xff0c;变量errno被初始化为0。很多库函数在执行过程中遇到错误时就会…

oracle篇—19c新特性自动索引介绍

☘️博主介绍☘️&#xff1a; ✨又是一天没白过&#xff0c;我是奈斯&#xff0c;DBA一名✨ ✌✌️擅长Oracle、MySQL、SQLserver、Linux&#xff0c;也在积极的扩展IT方向的其他知识面✌✌️ ❣️❣️❣️大佬们都喜欢静静的看文章&#xff0c;并且也会默默的点赞收藏加关注❣…

unity SqLite读取行和列

项目文件 链接&#xff1a;https://pan.baidu.com/s/1BabHvQ-y0kX_w15r7UvIGQ 提取码&#xff1a;emsg –来自百度网盘超级会员V6的分享 using System.Collections; using System.Collections.Generic; using UnityEngine; using Mono.Data.Sqlite; using System; using Syste…

【计算机网络】【新加坡南洋理工大学】【Computer Control Network】【广域网和局域网简介】【中英对照(自译)】

一、说明 仅供学习使用。 二、广域网&#xff08;WAN&#xff09;和局域网&#xff08;LAN&#xff09;简介

21所考408的院校有哪些?

计算机考研一直是考研的热门&#xff0c;那么在决定要参加计算机考研的时候&#xff0c;就要确定自己的复习方向&#xff0c;主流的复习方向有两类&#xff0c;一类是统考&#xff0c;也就是大家常说的408&#xff0c;还有一类是自命题&#xff0c;每一个学校的自命题都有所区别…

Windows10配置Maven环境变量

Windows10配置Maven环境变量 1.首先鼠标右键【此电脑】&#xff0c;点击【属性】2.点击【高级系统设置】3.点击【环境变量】4.创建【MAVEN_HOME】变量&#xff0c;变量值为maven的安装目录5.点击【Path】变量&#xff0c;点击【编辑】按钮6.点击【新建】按钮&#xff0c;输入【…