Android LiveData学习总结(源码级理解)
LiveData 工作原理
- 数据持有与观察者管理:
LiveData内部维护着一个数据对象和一个观察者列表。当调用observe方法注册观察者时,会将LifecycleOwner和Observer包装成LifecycleBoundObserver对象并添加到观察者列表中。 - 生命周期感知:
LifecycleBoundObserver实现了LifecycleEventObserver接口,能够监听LifecycleOwner的生命周期变化。当LifecycleOwner进入活跃状态(STARTED或RESUMED)时,LiveData会将最新数据发送给该观察者;当LifecycleOwner进入销毁状态(DESTROYED)时,LiveData会自动移除该观察者,避免内存泄漏。 - 数据更新通知:当调用
setValue(主线程)或postValue(子线程)方法更新数据时,LiveData会检查所有观察者的生命周期状态,只有处于活跃状态的观察者才会收到onChanged方法的调用,从而更新 UI。
整体架构与核心类
LiveData 相关的核心类主要有 LiveData、Observer、LifecycleOwner 和 Lifecycle。
LiveData:数据持有者类,负责存储数据并通知观察者数据的变化。Observer:观察者接口,定义了数据变化时的回调方法。LifecycleOwner:具有生命周期的组件,如Activity、Fragment,实现了该接口。Lifecycle:用于跟踪组件的生命周期状态。
工作流程与源码解析
1. 创建 LiveData 对象
LiveData<String> liveData = new MutableLiveData<>();
MutableLiveData 是 LiveData 的子类,它提供了 setValue() 和 postValue() 方法来更新数据。
2. 注册观察者
liveData.observe(this, new Observer<String>() {@Overridepublic 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() {@Overridepublic 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 具有良好的内存管理和用户体验。
相关文章:
Android LiveData学习总结(源码级理解)
LiveData 工作原理 数据持有与观察者管理:LiveData 内部维护着一个数据对象和一个观察者列表。当调用 observe 方法注册观察者时,会将 LifecycleOwner 和 Observer 包装成 LifecycleBoundObserver 对象并添加到观察者列表中。生命周期感知:L…...
Pandas进行数据预处理(标准化数据)③
数据标准化处理代码解析 数据标准化处理代码解析课前预习1. 离差标准化(Min - Max Scaling)结果2. 标准差标准化(Standard Scaling)结果3. 小数定标标准化(Decimal Scaling)结果 代码整体概述代码详细解析1…...
vue里provide作用:将一组全局方法注入到 Vue 应用的所有子组件中
在 Vue.js 中, provide(mainFunc, {...}) 是依赖注入(Dependency Injection)的提供者(provider)部分,它的作用是: 功能说明 : 将一组全局方法注入到 Vue 应用的所有子组件中子组件可以通过 inject 接收这些方法 import { provi…...
基于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:此项配置填写在一步API官网创建API令牌,一键直达API令牌创建页面创建API令牌步骤请参考API Key的获取和使用API Host:此项配置填写https://yibuapi.com/v1查看支持的模型请参考这篇教程模型在线查询 2. ChatBox网页版配…...
4.vtk光照vtkLight
文章目录 VTK中的光照1. vtkLight 的两种类型:位置光照和方向光照2. vtkLight 的常用方法3. 方法命名风格4. vtkProp 的可见性与 vtkLight 的开关 示例 VTK中的光照 vtkLight: 用于定义一个或多个光源。每个光源可以有其颜色、位置、焦点等属性。 vtkActor: 每个vtk…...
【速写】formatting_func与target_modules的细节(peft)
文章目录 SFTTrainer的构造参数版本差异怎么写formatting_func?关于lora_config中的target_modules能否在target_modules中指定特定某个模块? 以下面的示例pipeline为案: # -*- coding: utf8 -*- # author: caoyang # email: caoyangstu.sufe.edu.cnfr…...
YOLOv2学习笔记
YOLOv2 背景 YOLOv2是YOLO的第二个版本,其目标是显著提高准确性,同时使其更快 相关改进: 添加了BN层——Batch Norm采用更高分辨率的网络进行分类主干网络的训练 Hi-res classifier去除了全连接层,采用卷积层进行模型的输出&a…...
第十五届蓝桥杯----数字串个数\Python
目录 问题: 思想: 代码: 问题: Q:小蓝想要构造出一个长度为 10000 的数字字符串,有以下要求: 1) 小蓝不喜欢数字 0 ,所以数字字符串中不可以出现 0 ; 2) 小蓝喜欢数字 3 和 7 ,所以数字字符串中必须…...
【YOLOv8改进 - 卷积Conv】PConv(Pinwheel-shaped Conv): 风车状卷积用于红外小目标检测, 复现!
YOLOv8目标检测创新改进与实战案例专栏 专栏目录: YOLOv8有效改进系列及项目实战目录 包含卷积,主干 注意力,检测头等创新机制 以及 各种目标检测分割项目实战案例 专栏链接: YOLOv8基础解析+创新改进+实战案例 文章目录 YOLOv8目标检测创新改进与实战案例专栏介绍摘要文章链…...
LeetCode:链表
160. 相交链表 /*** 单链表的定义* Definition for singly-linked list.* public class ListNode {* int val;* ListNode next;* ListNode(int x) {* val x;* next null;* }* }*/ public class Solution {public ListNode getIntersectionN…...
Dockerfile项目实战-单阶段构建Vue2项目
单阶段构建镜像-Vue2项目 1 项目层级目录 以下是项目的基本目录结构: 2 Node版本 博主的Windows电脑安装了v14.18.3的node.js版本,所以直接使用本机电脑生成项目,然后拷到了 Centos 7 里面 # 查看本机node版本 node -v3 创建Vue2项目 …...
音视频小白系统入门笔记-0
本系列笔记为博主学习李超老师课程的课堂笔记,仅供参阅 音视频小白系统入门课 音视频基础ffmpeg原理 绪论 ffmpeg推流 ffplay/vlc拉流 使用rtmp协议 ffmpeg -i <source_path> -f flv rtmp://<rtmp_server_path> 为什么会推流失败? 默认…...
Zabbix 简介+部署+对接Grafana(详细部署!!)
目录 一.Zabbix简介 1.Zabbix是什么 2.Zabbix工作原理(重点) 3.Zabbix 的架构(重点) 1.服务端 2.客户端: 4.Zabbix和Prometheus区别 二.Zabbix 部署 1.前期准备 2.安装zabbix软件源和组件 3.安装数据库…...
C++: Initialization and References to const 初始化和常引用
cpp primer 5e, P97. 理解 这是一段很容易被忽略、 但是又非常重要的内容。 In 2.3.1 (p. 51) we noted that there are two exceptions to the rule that the type of a reference must match the type of the object to which it refers. The first exception is that we …...
Ubuntu2404装机指南
因为原来的2204升级到2404后直接嘎了,于是要重新装一下Ubuntu2404 Ubuntu系统下载 | Ubuntuhttps://cn.ubuntu.com/download我使用的是balenaEtcher将iso文件烧录进U盘后,使用u盘安装,默认选的英文版本, 安装后,安装…...
职坐标:智慧城市未来发展的核心驱动力
内容概要 智慧城市的演进正以颠覆性创新重构人类生存空间,其发展脉络由物联网、人工智能与云计算三大技术支柱交织而成。这些技术不仅推动城市治理从经验决策转向数据驱动模式,更通过实时感知与智能分析,实现交通、能源等领域的精准调控。以…...
DAY 45 leetcode 28的kmp算法实现
KMP算法的思路 例: 文本串:a a b a a b a a f 模式串:a a b a a f 两个指针分别指向上下两串,当出现分歧时,并不将上下的都重新回退,而是利用“next数组”获取已经比较过的信息,上面的指针不…...
从代码学习深度学习 - 自注意力和位置编码 PyTorch 版
这里写自定义目录标题 前言一、自注意力:Transformer 的核心1.1 多头注意力机制的实现1.2 缩放点积注意力1.3 掩码和序列处理1.4 自注意力示例二、位置编码:为序列添加位置信息2.1 位置编码的实现2.2 可视化位置编码总结前言 深度学习近年来在自然语言处理、计算机视觉等领域…...
设计和实现一个基于 DDS(直接数字频率合成) 的波形发生器
设计和实现一个基于 DDS(直接数字频率合成) 的波形发生器 1. 学习和理解IP软核和DDS 关于 IP 核的使用方法 IP 核:在 FPGA 设计中,IP 核(Intellectual Property Core)是由硬件描述语言(HDL&a…...
AWS IAM权限详解:10个关键权限及其安全影响
1. 引言 在AWS (Amazon Web Services) 环境中,Identity and Access Management (IAM) 是确保云资源安全的核心组件。本文将详细解析10个关键的IAM权限,这些权限对AWS的权限管理至关重要,同时也可能被用于权限提升攻击。深入理解这些权限对于加强AWS环境的安全性至关重要。 2.…...
UniRig ,清华联合 VAST 开源的通用自动骨骼绑定框架
UniRig是清华大学计算机系与VAST联合开发的前沿自动骨骼绑定框架,专为处理复杂且多样化的3D模型而设计。基于强大的自回归模型和骨骼点交叉注意力机制,UniRig能够生成高质量的骨骼结构和精确的蒙皮权重,大幅提升动画制作的效率和质量。 UniR…...
DELL电脑开机进入自检界面
疑难解答 - 如何解决开机直接进入BIOS画面 添加链接描述 一、DELL电脑开机自检提示please run setup program 未设置一天中的时间-请运行安装程序(Time-of-day not set - please run SETUP program) 配置信息无效-请运行安装程序(Invalid configuration information - ple…...
分库分表-除了hash分片还有别的吗?
在分库分表的设计中,除了常见的 Hash 分片,还有多种策略根据业务场景灵活选择。以下是几种主流的分库分表策略及其应用场景、技术实现和优缺点分析,结合项目经验(如标易行投标服务平台的高并发场景)进行说明: 一、常见分库分表策略 1. 范围分片(Range Sharding) 原理:…...
Spring Cloud初探之使用load balance包做负载均衡(三)
一、背景说明 基于前一篇文章《Spring Cloud初探之nacos服务注册管理(二)》,我们已经将服务注册到nacos。接下来继续分析如何用Spring cloud的load balance做负载均衡。 load balance是客户端负载均衡组件。本质是调用方拿到所有注册的服务实例列表,然…...
MySQL 数据库备份和恢复全指南
MySQL 是一款常用的开源数据库系统,在日常运维中,数据备份和恢复是系统管理的重要一环。本文将细致介绍 MySQL 两大备份方案—— mysqldump 和 XtraBackup,包括备份方式、恢复步骤、定时脚本、远程备份和常见问题处理方案。 一、mysqldump 备…...
Linux 命令全解析:从零开始掌握 Linux 命令行
Linux 作为一款强大的开源操作系统,广泛应用于服务器、嵌入式系统以及超级计算机领域。掌握 Linux 命令行技能,是每一位开发者和系统管理员的必备能力。本文将从基础开始,为你详细介绍常用的 Linux 命令,以及它们的使用场景和示例…...
vector常用的接口和底层
一.vector的构造函数 我们都是只讲常用的。 这四个都是比较常用的。 第一个简单来看就是无参构造,是通过一个无参的对象来对我们的对象进行初始化的,第一个我们常用来当无参构造来使用。 第二个我们常用的就是通过多个相同的数字来初始化一个vector。 像…...
VMware安装Ubuntu实战分享
1.前期准备 1. 硬件要求 确保您的计算机满足以下基本硬件要求,以便顺利运行 VMware 和 Ubuntu: 处理器: 至少支持虚拟化技术(如 Intel VT-x 或 AMD-V)。可以在 BIOS 设置中启用此功能。 内存: 至少 4GB …...
