Android LiveData学习总结(源码级理解)

news2025/4/16 15:51:58

LiveData 工作原理

  • 数据持有与观察者管理LiveData 内部维护着一个数据对象和一个观察者列表。当调用 observe 方法注册观察者时,会将 LifecycleOwner 和 Observer 包装成 LifecycleBoundObserver 对象并添加到观察者列表中。
  • 生命周期感知LifecycleBoundObserver 实现了 LifecycleEventObserver 接口,能够监听 LifecycleOwner 的生命周期变化。当 LifecycleOwner 进入活跃状态(STARTED 或 RESUMED)时,LiveData 会将最新数据发送给该观察者;当 LifecycleOwner 进入销毁状态(DESTROYED)时,LiveData 会自动移除该观察者,避免内存泄漏。
  • 数据更新通知:当调用 setValue(主线程)或 postValue(子线程)方法更新数据时,LiveData 会检查所有观察者的生命周期状态,只有处于活跃状态的观察者才会收到 onChanged 方法的调用,从而更新 UI。

整体架构与核心类

LiveData 相关的核心类主要有 LiveDataObserverLifecycleOwner 和 Lifecycle

  • LiveData:数据持有者类,负责存储数据并通知观察者数据的变化。
  • Observer:观察者接口,定义了数据变化时的回调方法。
  • LifecycleOwner:具有生命周期的组件,如 ActivityFragment,实现了该接口。
  • Lifecycle:用于跟踪组件的生命周期状态。

工作流程与源码解析

1. 创建 LiveData 对象
LiveData<String> liveData = new MutableLiveData<>();

MutableLiveData 是 LiveData 的子类,它提供了 setValue() 和 postValue() 方法来更新数据。

2. 注册观察者
liveData.observe(this, new Observer<String>() {
    @Override
    public void onChanged(String s) {
        // 数据变化时的回调
    }
});

下面是 observe() 方法的源码:

@MainThread
public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer) {
    assertMainThread("observe");
    if (owner.getLifecycle().getCurrentState() == DESTROYED) {
        // 如果 LifecycleOwner 已经销毁,直接返回
        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);
}
  • observe() 方法首先检查是否在主线程中调用,然后检查 LifecycleOwner 的状态,如果已经销毁则直接返回。
  • 创建 LifecycleBoundObserver 对象,它是 ObserverWrapper 的子类,实现了 LifecycleEventObserver 接口,用于监听 LifecycleOwner 的生命周期变化。
  • 将 observer 和 LifecycleBoundObserver 包装对象存入 mObservers 集合中。
  • 最后将 LifecycleBoundObserver 注册到 LifecycleOwner 的生命周期观察者列表中。
3. 更新数据

可以使用 setValue() 或 postValue() 方法更新 LiveData 中的数据。

// 在主线程中更新数据
((MutableLiveData<String>) liveData).setValue("new value");

// 在子线程中更新数据
((MutableLiveData<String>) liveData).postValue("new value");

setValue() 方法的源码:

@MainThread
protected void setValue(T value) {
    assertMainThread("setValue");
    mVersion++;
    mData = value;
    dispatchingValue(null);
}
  • setValue() 方法首先检查是否在主线程中调用,然后更新数据的版本号和数据值。
  • 调用 dispatchingValue() 方法通知所有观察者数据发生了变化。

postValue() 方法的源码:

protected void postValue(T value) {
    boolean postTask;
    synchronized (mDataLock) {
        postTask = mPendingData == NOT_SET;
        mPendingData = value;
    }
    if (!postTask) {
        return;
    }
    ArchTaskExecutor.getInstance().postToMainThread(mPostValueRunnable);
}
  • postValue() 方法用于在子线程中更新数据,它会将数据存入 mPendingData 中,并通过 ArchTaskExecutor 将更新操作切换到主线程中执行。
4. 通知观察者

dispatchingValue() 方法用于通知观察者数据发生了变化:

void dispatchingValue(@Nullable ObserverWrapper initiator) {
    if (mDispatchingValue) {
        mDispatchInvalidated = true;
        return;
    }
    mDispatchingValue = true;
    do {
        mDispatchInvalidated = false;
        if (initiator != null) {
            considerNotify(initiator);
            initiator = null;
        } else {
            for (Iterator<Map.Entry<Observer<? super T>, ObserverWrapper>> iterator =
                    mObservers.iteratorWithAdditions(); iterator.hasNext(); ) {
                considerNotify(iterator.next().getValue());
                if (mDispatchInvalidated) {
                    break;
                }
            }
        }
    } while (mDispatchInvalidated);
    mDispatchingValue = false;
}
  • dispatchingValue() 方法会遍历所有的观察者,并调用 considerNotify() 方法通知每个观察者。

considerNotify() 方法的源码:

private void considerNotify(ObserverWrapper observer) {
    if (!observer.mActive) {
        return;
    }
    if (!observer.shouldBeActive()) {
        observer.activeStateChanged(false);
        return;
    }
    if (observer.mLastVersion >= mVersion) {
        return;
    }
    observer.mLastVersion = mVersion;
    //noinspection unchecked
    ((Observer<T>) observer.mObserver).onChanged((T) mData);
}
  • considerNotify() 方法会检查观察者的活跃状态和数据版本号,如果观察者不活跃或数据版本号没有变化,则不进行通知。
  • 如果满足条件,则调用观察者的 onChanged() 方法,将最新的数据传递给观察者。

扩展追问

LiveData 的 postValue 方法用于在后台线程中更新 LiveData 的值。不过,使用这个方法时可能会出现值丢失的情况,下面结合源码深入分析其原因。

postValue 方法源码分析

postValue 方法的实现位于 LiveData 类中,以下是 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() {
    @Override
    public void run() {
        Object newValue;
        synchronized (mDataLock) {
            newValue = mPendingData;
            mPendingData = NOT_SET;
        }
        setValue((T) newValue);
    }
};

值可能丢失的原因

1. 多线程并发调用 postValue

postValue 方法会将新值存储在 mPendingData 中,并通过 ArchTaskExecutor 将一个 Runnable 任务(mPostValueRunnable)发送到主线程执行。当在多线程环境下频繁调用 postValue 方法时,可能会出现值丢失的情况。

具体来说,postValue 方法中有一个同步块:

synchronized (mDataLock) {
    postTask = mPendingData == NOT_SET;
    mPendingData = value;
}

在这个同步块中,会判断 mPendingData 是否为 NOT_SET(表示没有待处理的值)。如果不是 NOT_SET,则 postTask 为 false,不会再次发送 Runnable 任务到主线程。这意味着,如果在 Runnable 任务还未执行时,又有新的 postValue 调用,新的值会覆盖 mPendingData 中的旧值,而旧值就会丢失。

例如,假设在后台线程中有两个线程同时调用 postValue 方法:

// 线程 1
liveData.postValue("value1");
// 线程 2
liveData.postValue("value2");

如果线程 2 在 mPostValueRunnable 还未执行时就调用了 postValue 方法,那么 mPendingData 中的值会从 "value1" 被覆盖为 "value2",最终 mPostValueRunnable 执行时,setValue 方法只会将 "value2" 发送给观察者,"value1" 就丢失了。

2. 主线程任务队列的延迟

postValue 方法会将 mPostValueRunnable 任务发送到主线程执行,而主线程有自己的任务队列。如果主线程比较繁忙,mPostValueRunnable 任务可能会延迟执行。在这个延迟期间,如果有新的 postValue 调用,同样会导致值丢失。

例如,当主线程正在处理大量的 UI 绘制任务时,mPostValueRunnable 任务可能会被阻塞在队列中。此时,如果有新的 postValue 调用,mPendingData 中的值会被更新,旧的值就会丢失。

解决方案

如果需要确保每个值都能被处理,可以使用 setValue 方法,但 setValue 方法必须在主线程中调用。如果需要在后台线程中更新值,可以考虑使用 MutableLiveData 的子类,自定义实现更新逻辑,确保每个值都能被正确处理。

// 在主线程中使用 setValue 方法更新值
liveData.setValue("newValue");

综上所述,LiveData 的 postValue 方法由于多线程并发调用和主线程任务队列的延迟,可能会导致值丢失。在使用时需要根据具体情况选择合适的更新方法。

总结

LiveData 的工作原理主要基于观察者模式和生命周期感知机制。

通过 observe() 方法注册观察者,将观察者与 LifecycleOwner 关联起来,当 LifecycleOwner 的生命周期状态发生变化时,LiveData 会自动处理观察者的活跃状态。

当数据更新时,LiveData 会通知所有活跃的观察者,确保只有在组件处于活跃状态时才更新 UI。这种设计使得 LiveData 具有良好的内存管理和用户体验。

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

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

相关文章

Pandas进行数据预处理(标准化数据)③

数据标准化处理代码解析 数据标准化处理代码解析课前预习1. 离差标准化&#xff08;Min - Max Scaling&#xff09;结果2. 标准差标准化&#xff08;Standard Scaling&#xff09;结果3. 小数定标标准化&#xff08;Decimal Scaling&#xff09;结果 代码整体概述代码详细解析1…

基于uniapp 实现画板签字

直接上效果图 代码 <template><view class"container"><!-- 签名画布 --><view class"canvas-container"><canvas canvas-id"signCanvas" class"sign-canvas"touchstart"handleTouchStart"touc…

JDBC 初认识、速了解

目录 一. JDBC的简介 1. 数据的持久化 2. 什么是JDBC 二. JDBC中常用的类和接口 1. Driver 接口 2. DriverManager 类 3. Connection 接口 4. Statement 接口 5. PreparedStatement接口 6. ResultSet 接口 三. 总结 前言 从现在开始就来讲解JDBC的相关知识了 本文的…

(2025亲测可用)Chatbox多端一键配置Claude/GPT/DeepSeek-网页端配置

1. 资源准备 API Key&#xff1a;此项配置填写在一步API官网创建API令牌&#xff0c;一键直达API令牌创建页面创建API令牌步骤请参考API Key的获取和使用API Host&#xff1a;此项配置填写https://yibuapi.com/v1查看支持的模型请参考这篇教程模型在线查询 2. ChatBox网页版配…

4.vtk光照vtkLight

文章目录 VTK中的光照1. vtkLight 的两种类型&#xff1a;位置光照和方向光照2. vtkLight 的常用方法3. 方法命名风格4. vtkProp 的可见性与 vtkLight 的开关 示例 VTK中的光照 vtkLight: 用于定义一个或多个光源。每个光源可以有其颜色、位置、焦点等属性。 vtkActor: 每个vtk…

YOLOv2学习笔记

YOLOv2 背景 YOLOv2是YOLO的第二个版本&#xff0c;其目标是显著提高准确性&#xff0c;同时使其更快 相关改进&#xff1a; 添加了BN层——Batch Norm采用更高分辨率的网络进行分类主干网络的训练 Hi-res classifier去除了全连接层&#xff0c;采用卷积层进行模型的输出&a…

【YOLOv8改进 - 卷积Conv】PConv(Pinwheel-shaped Conv): 风车状卷积用于红外小目标检测, 复现!

YOLOv8目标检测创新改进与实战案例专栏 专栏目录: YOLOv8有效改进系列及项目实战目录 包含卷积,主干 注意力,检测头等创新机制 以及 各种目标检测分割项目实战案例 专栏链接: YOLOv8基础解析+创新改进+实战案例 文章目录 YOLOv8目标检测创新改进与实战案例专栏介绍摘要文章链…

Dockerfile项目实战-单阶段构建Vue2项目

单阶段构建镜像-Vue2项目 1 项目层级目录 以下是项目的基本目录结构&#xff1a; 2 Node版本 博主的Windows电脑安装了v14.18.3的node.js版本&#xff0c;所以直接使用本机电脑生成项目&#xff0c;然后拷到了 Centos 7 里面 # 查看本机node版本 node -v3 创建Vue2项目 …

Zabbix 简介+部署+对接Grafana(详细部署!!)

目录 一.Zabbix简介 1.Zabbix是什么 2.Zabbix工作原理&#xff08;重点&#xff09;​ 3.Zabbix 的架构&#xff08;重点&#xff09;​ 1.服务端 2.客户端&#xff1a; 4.Zabbix和Prometheus区别 二.Zabbix 部署 1.前期准备 2.安装zabbix软件源和组件 3.安装数据库…

Ubuntu2404装机指南

因为原来的2204升级到2404后直接嘎了&#xff0c;于是要重新装一下Ubuntu2404 Ubuntu系统下载 | Ubuntuhttps://cn.ubuntu.com/download我使用的是balenaEtcher将iso文件烧录进U盘后&#xff0c;使用u盘安装&#xff0c;默认选的英文版本&#xff0c; 安装后&#xff0c;安装…

Spring Cloud初探之使用load balance包做负载均衡(三)

一、背景说明 基于前一篇文章《Spring Cloud初探之nacos服务注册管理(二)》&#xff0c;我们已经将服务注册到nacos。接下来继续分析如何用Spring cloud的load balance做负载均衡。 load balance是客户端负载均衡组件。本质是调用方拿到所有注册的服务实例列表&#xff0c;然…

vector常用的接口和底层

一.vector的构造函数 我们都是只讲常用的。 这四个都是比较常用的。 第一个简单来看就是无参构造&#xff0c;是通过一个无参的对象来对我们的对象进行初始化的&#xff0c;第一个我们常用来当无参构造来使用。 第二个我们常用的就是通过多个相同的数字来初始化一个vector。 像…

【2025年3月中科院1区SCI】Rating entropy等级熵及5种多尺度,特征提取、故障诊断新方法!

引言 2025年3月&#xff0c;研究者在国际机械领域顶级期刊《Mechanical Systems and Signal Processing》&#xff08;JCR 1区&#xff0c;中科院1区 Top&#xff0c;IF&#xff1a;7.9&#xff09;上以“Rating entropy and its multivariate version”为题发表科学研究成果。…

【AI学习】李宏毅老师讲AI Agent摘要

在b站听了李宏毅2025最新的AI Agent教程&#xff0c;简单易懂&#xff0c;而且紧跟发展&#xff0c;有大量最新的研究进展。 教程中引用了大量论文&#xff0c;为了方便将来阅读相关论文&#xff0c;进一步深入理解&#xff0c;做了截屏纪录。 同时也做一下分享。 根据经验调整…

Nacos-Controller 2.0:使用 Nacos 高效管理你的 K8s 配置

作者&#xff1a;濯光、翼严 Kubernetes 配置管理的局限 目前&#xff0c;在 Kubernetes 集群中&#xff0c;配置管理主要通过 ConfigMap 和 Secret 来实现。这两种资源允许用户将配置信息通过环境变量或者文件等方式&#xff0c;注入到 Pod 中。尽管 Kubernetes 提供了这些强…

【BUG】Redis RDB快照持久化及写操作禁止问题排查与解决

1 问题描述 在使用Redis 的过程中&#xff0c;遇到如下报错&#xff0c;错误信息是 “MISCONF Redis is configured to save RDB snapshots, but it is currently not able to persist on disk...”&#xff0c;记录下问题排查过程。 2 问题排查与解决 该错误提示表明&#…

java分页实例

引言 在现代Web应用和移动应用中&#xff0c;面对大量数据的展示&#xff0c;分页技术成为了提升用户体验和优化数据加载效率的关键手段。尤其是在MySQL数据库环境中&#xff0c;合理运用分页查询不仅能显著减少服务器负载&#xff0c;还能提升数据访问速度&#xff0c;为用户提…

【Linux篇】ELF文件及其加载与动态链接机制

ELF文件及其加载与动态链接机制 一. EFL文件1.1 ELF文件结构二. ELF文件形成与加载2.1 ELF形成可执行2.2 ELF控制性文件的加载2.2.1总结 三. ELF加载与进程地址空间3.1 动态链接与动态库加载3.1.1 进程如何看到动态库 3.2 全局偏移量表GOT(global offset table&#xff09;3.2.…

经典算法 判断一个图中是否有环

判断一个图中是否有环 问题描述 给一个以0 0结尾的整数对列表&#xff0c;除0 0外的每两个整数表示一条连接了这两个节点的边。假设节点编号不超过100000大于0。你只要判断由这些节点和边构成的图中是否存在环。存在输出YES&#xff0c;不存在输出NO。 输入样例1 6 8 5 3 …

AI与深度伪造技术:如何识别和防范AI生成的假视频和假音频?

引言&#xff1a;深度伪造的崛起 近年来&#xff0c;人工智能技术迅猛发展&#xff0c;其中深度伪造&#xff08;Deepfake&#xff09; 技术尤为引人注目。这项技术利用深度学习和神经网络&#xff0c;可以轻松生成高度逼真的假视频和假音频&#xff0c;使人物的面部表情、语音…