Jetpack LiveData 使用与原理解析

一、引言

在 Android 开发中,数据的变化需要及时反映到界面上是一个常见的需求。然而,传统的方式可能会导致代码复杂、难以维护,并且容易出现内存泄漏等问题。Jetpack 组件中的 LiveData 为我们提供了一种优雅的解决方案,它是一种可观察的数据持有者类,具有生命周期感知能力,能够确保数据更新时只在合适的生命周期状态下通知观察者,从而避免了内存泄漏和空指针异常等问题。本文将详细介绍 LiveData 的使用方法,并深入剖析其源码原理。

二、LiveData 基本使用

2.1 添加依赖

要使用 LiveData,需要在项目的 build.gradle 文件中添加以下依赖:

implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.6.1'

2.2 创建 LiveData 对象

LiveData 是一个抽象类,通常使用它的子类 MutableLiveData 来创建可修改的 LiveData 对象。以下是一个简单的示例:

import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModelclass MyViewModel : ViewModel() {// 创建一个 MutableLiveData 对象,用于存储整数类型的数据val currentNumber: MutableLiveData<Int> by lazy {MutableLiveData<Int>().also {it.value = 0}}// 增加数字的方法fun incrementNumber() {currentNumber.value = (currentNumber.value ?: 0) + 1}
}

在这个示例中,我们创建了一个 MyViewModel 类,其中包含一个 MutableLiveData 类型的 currentNumber 对象,并提供了一个方法 incrementNumber 来更新该对象的值。

2.3 观察 LiveData 对象

在 Activity 或 Fragment 中,我们可以通过 observe 方法来观察 LiveData 对象的变化,并在数据更新时执行相应的操作。以下是在 Activity 中观察 currentNumber 的示例:

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.lifecycle.ViewModelProvider
import com.example.livedatademo.databinding.ActivityMainBindingclass MainActivity : AppCompatActivity() {private lateinit var binding: ActivityMainBindingprivate lateinit var viewModel: MyViewModeloverride fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)binding = ActivityMainBinding.inflate(layoutInflater)setContentView(binding.root)// 获取 ViewModel 实例viewModel = ViewModelProvider(this).get(MyViewModel::class.java)// 观察 LiveData 对象viewModel.currentNumber.observe(this) { newNumber ->// 当数据更新时,更新界面显示binding.numberTextView.text = newNumber.toString()}// 设置按钮点击事件,调用 ViewModel 中的方法更新数据binding.incrementButton.setOnClickListener {viewModel.incrementNumber()}}
}

MainActivity 中,我们通过 observe 方法观察 currentNumber 的变化,当 currentNumber 的值发生改变时,会触发 lambda 表达式中的代码,更新界面上的文本显示。

2.4 其他使用场景

转换 LiveData

LiveData 提供了一些转换方法,如 mapswitchMap,用于对 LiveData 中的数据进行转换。以下是使用 map 方法的示例:

import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Transformations
import androidx.lifecycle.ViewModelclass MyViewModel : ViewModel() {private val _inputData = MutableLiveData<String>()val outputData: LiveData<String> = Transformations.map(_inputData) { input ->// 对输入数据进行转换"Transformed: $input"}fun setInputData(input: String) {_inputData.value = input}
}

在这个示例中,我们使用 Transformations.map 方法将 _inputData 中的数据进行转换,并将转换后的结果存储在 outputData 中。

合并 LiveData

可以使用 MediatorLiveData 来合并多个 LiveData 对象,当其中任何一个 LiveData 对象的数据发生变化时,MediatorLiveData 都会收到通知。以下是一个简单的示例:

import androidx.lifecycle.MediatorLiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModelclass MyViewModel : ViewModel() {private val _source1 = MutableLiveData<String>()private val _source2 = MutableLiveData<String>()val mergedData: MediatorLiveData<String> = MediatorLiveData<String>().apply {// 添加第一个数据源addSource(_source1) { value ->value?.let {this.value = "Source 1: $it"}}// 添加第二个数据源addSource(_source2) { value ->value?.let {this.value = "Source 2: $it"}}}fun setSource1Data(data: String) {_source1.value = data}fun setSource2Data(data: String) {_source2.value = data}
}

在这个示例中,MediatorLiveData 合并了 _source1_source2 两个 LiveData 对象,当其中任何一个数据源的数据发生变化时,mergedData 都会更新。

三、LiveData 源码原理解析

3.1 LiveData 核心类结构

LiveData 主要涉及以下几个核心类:

  • LiveData:抽象类,定义了 LiveData 的基本行为和接口。
  • MutableLiveDataLiveData 的子类,提供了 setValuepostValue 方法用于更新数据。
  • Observer:观察者接口,用于定义数据更新时的回调方法。
  • LifecycleBoundObserver:实现了 Observer 接口,并且具有生命周期感知能力,确保只在合适的生命周期状态下通知观察者。

3.2 数据更新机制

setValue 方法

setValue 方法用于在主线程中更新 LiveData 的值,并通知所有活跃的观察者。以下是 setValue 方法的源码:

@MainThread
protected void setValue(T value) {assertMainThread("setValue");mVersion++;mData = value;dispatchingValue(null);
}

setValue 方法中,首先会检查当前线程是否为主线程,然后更新数据的版本号和数据值,最后调用 dispatchingValue 方法来通知观察者。

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() {@SuppressWarnings("unchecked")@Overridepublic void run() {Object newValue;synchronized (mDataLock) {newValue = mPendingData;mPendingData = NOT_SET;}setValue((T) newValue);}
};

postValue 方法中,会将新的值存储在 mPendingData 中,并通过 ArchTaskExecutormPostValueRunnable 发送到主线程中执行。在 mPostValueRunnable 中,会调用 setValue 方法来更新数据并通知观察者。

3.3 观察者注册与通知机制

observe 方法

observe 方法用于注册一个具有生命周期感知能力的观察者。以下是 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 的状态。如果 LifecycleOwner 已经销毁,则直接返回。接着会创建一个 LifecycleBoundObserver 对象,并将其存储在 mObservers 中。最后,将 LifecycleBoundObserver 注册到 LifecycleOwner 的生命周期中。

dispatchingValue 方法

dispatchingValue 方法用于通知观察者数据更新。以下是 dispatchingValue 方法的源码:

private 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 方法

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;// 调用观察者的 onChanged 方法通知数据更新observer.mObserver.onChanged((T) mData);
}

considerNotify 方法中,会检查观察者的活跃状态和版本号,如果观察者不活跃或者版本号已经是最新的,则不会通知观察者。否则,会调用观察者的 onChanged 方法通知数据更新。

四、总结

LiveData 是 Jetpack 组件中一个非常实用的工具,它通过生命周期感知能力和数据更新通知机制,为开发者提供了一种简洁、安全的方式来处理数据和界面的交互。通过 setValuepostValue 方法可以更新数据,通过 observe 方法可以注册观察者。在源码层面,LiveData 通过 LifecycleBoundObserver 实现了生命周期感知,确保只在合适的生命周期状态下通知观察者。合理使用 LiveData 可以提高代码的可维护性和稳定性,避免内存泄漏和空指针异常等问题。在实际开发中,结合 ViewModel 等其他 Jetpack 组件,可以构建出更加高效、健壮的 Android 应用。

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

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

相关文章

Unity2D 五子棋 + Photon联网双人对战

开发环境配置 Unity版本2022.3 创建Photon账号以及申请Photon中国区服务 官网申请账号&#xff1a;Multiplayer Game Development Made Easy Photon Engine 中国区服务&#xff1a; 光子引擎photonengine中文站 成都动联无限科技有限公司(vibrantlink.com) 导入PUN2插件以及…

(UI自动化测试web端)第二篇:元素定位的方法_css定位之属性选择器

看代码里的【find_element_by_css_selector( )】( )里的表达式怎么写&#xff1f; 文章介绍了第四种写法属性选择器 &#xff0c;你要根据网页中的实际情况来判断自己到底要用哪一种方法来进行元素定位。每种方法都要多练习&#xff0c;全都熟了之后你在工作当中使用起来元素定…

预编译能否 100%防 sql 注入?

&#x1f31f; 什么是 SQL 注入&#xff1f; SQL 注入&#xff08;SQL Injection&#xff09;是指攻击者利用特殊输入&#xff0c;让数据库执行它本来不应该执行的代码&#xff0c;从而获取或篡改数据。 就像在考试的时候偷偷改题目&#xff0c;让老师改成你想要的内容&#…

第十五章 | Layer2、Rollup 与 ZK 技术实战解析

&#x1f4da; 第十五章 | Layer2、Rollup 与 ZK 技术实战解析 ——构建下一代高性能区块链应用&#xff0c;从 Solidity 到 zkSync&#xff01; ✅ 本章导读 Layer2 和零知识证明&#xff08;ZK&#xff09;正成为区块链发展的核心方向。 随着主网 Gas 居高不下、TPS 无法满…

2025-03-26 学习记录--C/C++-PTA 6-3 求链式表的表长

合抱之木&#xff0c;生于毫末&#xff1b;九层之台&#xff0c;起于累土&#xff1b;千里之行&#xff0c;始于足下。&#x1f4aa;&#x1f3fb; 一、题目描述 ⭐️ 6-3 求链式表的表长 本题要求实现一个函数&#xff0c;求链式表的表长。 函数接口定义&#xff1a; &…

【Linux】Linux_Ubuntu与Windows之间的文件传输

一、Linux终端命令的复制粘贴 1.打开linux 终端&#xff0c;输入以下命令&#xff1a;&#xff08;注意&#xff0c;需要联网&#xff09; 2.命令行下载&#xff1a; sudo apt-get autoremove open-vm-tools 3.命令行安装&#xff1a; sudo apt-get install open-vm-tools-…

Python Sanic面试题及参考答案

目录 Sanic 的事件循环机制与 uvloop 的关系 Sanic 的 Request/Response 对象生命周期如何管理?如何访问请求上下文? 对比 Sanic 与 Flask/Django 的异步处理模型差异 Sanic 的 Blueprint 机制如何实现模块化路由?如何处理跨蓝图中间件? 如何在 Sanic 中实现 WebSocket…

算法每日一练 (18)

&#x1f4a2;欢迎来到张翊尘的技术站 &#x1f4a5;技术如江河&#xff0c;汇聚众志成。代码似星辰&#xff0c;照亮行征程。开源精神长&#xff0c;传承永不忘。携手共前行&#xff0c;未来更辉煌&#x1f4a5; 文章目录 算法每日一练 (18)删除并获得点数题目描述解题思路解题…

VsCode启用右括号自动跳过(自动重写) - 自录制gif演示

VsCode启用右括号自动跳过(自动重写) - 自录制gif演示 前言 不知道大家在编程时候的按键习惯是怎样的。输入完左括号后编辑器一般会自动补全右括号&#xff0c;输入完左括号的内容后&#xff0c;是按→跳过右括号还是按)跳过右括号呢&#xff1f; for (int i 0; i < a.s…

用Python和Stable Diffusion生成AI动画:从图像到视频的全流程指南

引言 本文将演示如何通过Python代码实现基于文本提示的AI动画生成。我们将使用Stable Diffusion生成连贯图像帧,结合OpenCV合成视频,最终实现一个可自定义的动画生成 pipeline。 一、环境准备 1. 依赖安装 # 安装核心库 pip install diffusers transformers torch numpy …

【Git 常用指令速查表】

Git 常用指令速查表 Git 常用指令速查表目录1. 初始化仓库2. 提交代码流程3. 分支管理4. 远程仓库操作5. 撤销操作6. 查看状态与日志7. 其他实用指令完整操作示例常用场景速查表 Git 常用指令速查表 目录 初始化仓库提交代码流程分支管理远程仓库操作撤销操作查看状态与日志其…

分布式爬虫框架Scrapy-Redis实战指南

引言 在当今数字化的时代背景下&#xff0c;互联网技术的蓬勃兴起极大地改变了旅游酒店业的运营模式与市场格局。作为旅游产业链中的关键一环&#xff0c;酒店业的兴衰与互联网技术的应用程度紧密相连。分布式爬虫技术&#xff0c;尤其是基于 Scrapy 框架的 Scrapy-Redis 扩展…

爬虫:scrapy面试题大全(60个scrapy经典面试题和详解)

更多内容请见: 爬虫和逆向教程-专栏介绍和目录 文章目录 1. 什么是Scrapy?2. Scrapy 框架的组件及其作用?3. Scrapy的工作流程是什么?(运行机制)4. 如何创建一个Scrapy项目?5. 如何定义一个Spider?6. 如何在Scrapy中提取数据?7. Scrapy中的Item是什么?8. Scrapy中的P…

Leetcode12-整数转罗马数字

题目链接&#xff1a;12. 整数转罗马数字 - 力扣&#xff08;LeetCode&#xff09; 看题目限制输入1 < num < 3999&#xff0c;就直接用暴力法写了&#xff0c;还比较简单 代码&#xff1a; char* intToRoman(int num) {char *res (char*)malloc(100);int index 0;i…

WebMvcConfigurer 的 addResourceLocations

在 Spring Boot 的 addResourceLocations 方法中&#xff0c;file: 是一个 URL 前缀&#xff0c;用于指示资源的位置是本地文件系统路径。以下是详细解释&#xff1a; 一、file: 的作用 file: 是 Java 中用于表示本地文件系统的 URL 前缀。它告诉 Spring Boot&#xff0c;资源…

Spring Boot响应压缩配置与优化

一、核心工作机制 1.1 自动协商触发条件 Spring Boot的响应压缩功能基于智能协商机制&#xff0c;需同时满足以下条件方可触发&#xff1a; 客户端支持&#xff1a;请求头包含Accept-Encoding: gzip/deflate数据量阈值&#xff1a;响应体大小超过预设值&#xff08;默认2KB&…

JavaScript 改变 HTML 样式

JavaScript 改变 HTML 样式 JavaScript 改变 HTML 样式的核心是通过操作 DOM 元素的 CSS 属性或 类名 实现动态视觉效果。以下是具体方法与场景解析: 一、直接修改元素的 style 属性 通过 DOM 元素的 style 属性直接设置内联样式,优先级最高: // 修改单个样式 document.…

【vue】vue + vant实现上传图片添加水印

目录 方法1&#xff1a;使用HTML2canvas 说明&#xff1a; 优点 缺点 依赖安装 方法2&#xff1a;使用canvas结合vant中组件 增加水印方法 在vue组件中使用 要点 方法1&#xff1a;使用HTML2canvas 使用html2canvas来处理水印的生成&#xff0c;需要就给水印元素转换为…

【深度破解】爬虫反反爬核心技术实践:验证码识别与指纹伪装

一、反爬技术体系全景图 现代Web应用的常见反爬手段&#xff1a; mermaid&#xff1a; graph TDA[反爬体系] --> B[行为特征检测]A --> C[验证码体系]A --> D[指纹追踪]B --> B1[请求频率]B --> B2[鼠标轨迹]B --> B3[页面停留时间]C --> C1[图形验证码…

deepseek(2)——deepseek 关键技术

1 Multi-Head Latent Attention (MLA) MLA的核心在于通过低秩联合压缩来减少注意力键&#xff08;keys&#xff09;和值&#xff08;values&#xff09;在推理过程中的缓存&#xff0c;从而提高推理效率&#xff1a; c t K V W D K V h t c_t^{KV} W^{DKV}h_t ctKV​WDKVht​…