引言

在Android应用开发中,数据管理和界面更新一直是开发者面临的重大挑战。传统的开发方式常常导致Activity和Fragment变得臃肿,难以维护,且无法优雅地处理配置变更(如屏幕旋转)。Jetpack中的ViewModel和LiveData组件正是为解决这些问题而生,它们共同构成了Android应用架构的核心支柱。本文将深入探讨这两个组件的设计理念、使用方法和最佳实践。

一、ViewModel:生命周期感知的数据容器

1.1 ViewModel概述

ViewModel是设计用来以生命周期感知的方式存储和管理界面相关数据的组件。它主要有以下特点:

  • 生命周期感知:自动关联Activity/Fragment的生命周期

  • 配置变更存活:屏幕旋转等配置变更时数据不会丢失

  • 界面数据分离:促进关注点分离,减轻UI控制器的负担

  • 作用域限定:与特定的Activity或Fragment实例关联

1.2 ViewModel解决的问题

传统Android开发中常见的问题:

  • 数据丢失:配置变更导致Activity重建,临时数据丢失

  • 内存泄漏:异步操作持有Activity引用导致泄漏

  • 职责过重:Activity/Fragment同时处理UI逻辑和数据操作

  • 测试困难:UI逻辑与业务逻辑混杂,难以单元测试

1.3 基本使用

添加依赖
dependencies {def lifecycle_version = "2.5.1"implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
}
创建ViewModel
class MyViewModel : ViewModel() {private val _counter = MutableLiveData(0)val counter: LiveData<Int> = _counterfun increment() {_counter.value = (_counter.value ?: 0) + 1}
}
在Activity/Fragment中使用
class MainActivity : AppCompatActivity() {private lateinit var viewModel: MyViewModeloverride fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_main)// 获取ViewModel实例viewModel = ViewModelProvider(this).get(MyViewModel::class.java)// 观察LiveDataviewModel.counter.observe(this) { count ->textView.text = "Count: $count"}button.setOnClickListener {viewModel.increment()}}
}

1.4 ViewModel生命周期

ViewModel的生命周期与其关联的UI组件密切相关:

  • 创建:当首次请求ViewModel时(通常在onCreate中)

  • 存活:在整个Activity/Fragment的生命周期中保持存活

  • 销毁:当关联的Activity结束或Fragment分离时清除

 

 

二、LiveData:可观察的数据持有者

2.1 LiveData概述

LiveData是一种可观察的数据持有者类,具有生命周期感知能力,这意味着它只在Activity、Fragment或Service处于活跃生命周期状态时才会更新这些组件。

主要特点:

  • 数据可观察:当数据变化时通知观察者

  • 生命周期感知:自动管理订阅,避免内存泄漏

  • 自动取消订阅:当观察者处于非活跃状态时不接收更新

  • 最新数据保证:新观察者会立即收到最新数据

2.2 LiveData类型

  1. MutableLiveData:可变的LiveData基类

  2. MediatorLiveData:可合并多个LiveData源

  3. Transformations:提供map和switchMap操作符

2.3 基本使用

创建LiveData
class UserViewModel : ViewModel() {private val _user = MutableLiveData<User>()val user: LiveData<User> = _userfun loadUser(userId: String) {viewModelScope.launch {val loadedUser = repository.getUser(userId)_user.postValue(loadedUser)}}
}
观察LiveData
viewModel.user.observe(viewLifecycleOwner) { user ->// 更新UInameTextView.text = user.nameemailTextView.text = user.email
}

2.4 LiveData转换

map转换
val userName: LiveData<String> = Transformations.map(viewModel.user) { user ->"${user.firstName} ${user.lastName}"
}
switchMap转换
private val userId = MutableLiveData<String>()
val user: LiveData<User> = Transformations.switchMap(userId) { id ->repository.getUser(id)
}

三、ViewModel与LiveData的协同工作

ViewModel和LiveData通常一起使用,形成强大的数据管理组合:

  1. ViewModel:持有和管理与UI相关的数据

  2. LiveData:在ViewModel中暴露可观察的数据

  3. UI控制器:观察LiveData并更新界面

这种模式的优势:

  • 数据持久性:配置变更时数据不会丢失

  • 资源管理:自动取消不必要的更新

  • 测试便利:业务逻辑与UI逻辑分离

  • 响应式UI:数据变化自动反映到界面

四、高级用法与最佳实践

4.1 共享ViewModel

在Fragment间共享数据:

// 在Activity中获取
val sharedViewModel = ViewModelProvider(this).get(SharedViewModel::class.java)// 在Fragment中获取(同一个Activity范围内)
val sharedViewModel = ViewModelProvider(requireActivity()).get(SharedViewModel::class.java)

4.2 保存状态

处理进程死亡的情况(结合SavedStateHandle):

class SavedStateViewModel(private val state: SavedStateHandle) : ViewModel() {val counter: LiveData<Int> = state.getLiveData("counter", 0)fun increment() {state["counter"] = (counter.value ?: 0) + 1}
}

4.3 结合Repository模式

class UserViewModel(private val repository: UserRepository,savedStateHandle: SavedStateHandle
) : ViewModel() {private val userId: String = savedStateHandle["userId"] ?: throw IllegalArgumentException("Missing userId")val user: LiveData<User> = repository.getUser(userId).asLiveData()
}

4.4 测试ViewModel

@RunWith(JUnit4::class)
class MyViewModelTest {private lateinit var viewModel: MyViewModel@Beforefun setup() {viewModel = MyViewModel()}@Testfun increment_increasesCounter() {assertEquals(0, viewModel.counter.value)viewModel.increment()assertEquals(1, viewModel.counter.value)}
}

五、常见问题与解决方案

5.1 何时使用ViewModel vs SavedInstanceState

场景ViewModelSavedInstanceState
屏幕旋转✓ 保留✓ 保留
系统杀死进程✗ 丢失✓ 保留
大/复杂数据✓ 适合✗ 不适合
简单UI状态✓ 适合✓ 适合

5.2 LiveData vs Flow

特性LiveDataFlow
生命周期感知✓ 是✗ 否(需配合lifecycleScope)
多平台支持✗ 仅Android✓ 跨平台
复杂数据流✗ 有限✓ 强大
冷热流热流冷流

5.3 避免内存泄漏

最佳实践组合

随着Android开发的不断演进,ViewModel和LiveData仍然是构建健壮、可维护应用的基础。掌握这些组件不仅能提升应用质量,还能显著提高开发效率。

  • 不要在ViewModel中持有View/Activity/Fragment引用

  • 使用viewModelScope管理协程,自动取消

  • 对于Android资源,在ViewModel中提供清理方法

    override fun onCleared() {super.onCleared()// 清理资源
    }

    六、与现代架构组件的集成

    6.1 结合Room数据库

    @Dao
    interface UserDao {@Query("SELECT * FROM user WHERE id = :userId")fun getUser(userId: String): LiveData<User>
    }class UserRepository(private val userDao: UserDao) {fun getUser(userId: String): LiveData<User> {return userDao.getUser(userId)}
    }

    6.2 结合Data Binding

    <layout><data><variable name="viewmodel"type="com.example.MyViewModel" /></data><TextViewandroid:text="@{String.valueOf(viewmodel.counter)}"... />
    </layout>

    6.3 结合Compose

    @Composable
    fun CounterScreen(viewModel: CounterViewModel = viewModel()) {val count by viewModel.counter.observeAsState(0)Column {Text(text = "Count: $count")Button(onClick = { viewModel.increment() }) {Text("Increment")}}
    }

    七、总结

    ViewModel和LiveData作为Android Jetpack架构组件的核心部分,为现代Android应用开发提供了强大而优雅的解决方案:

  • ViewModel

    • 管理界面相关数据

    • 在配置变更时保留数据

    • 减轻UI控制器的负担

  • LiveData

    • 提供响应式数据流

    • 自动管理生命周期

    • 防止内存泄漏

  • 使用ViewModel保存和管理UI数据

  • 通过LiveData暴露数据给UI

  • 结合Repository模式处理数据源

  • 使用协程处理异步操作

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

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

相关文章

Python数据分析案例79——基于征信数据开发信贷风控模型

背景 虽然模型基本都是表格数据那一套了&#xff0c;算法都没什么新鲜点&#xff0c;但是本次数据还是很值得写个案例的&#xff0c;有征信数据&#xff0c;各种&#xff0c;个人&#xff0c;机构&#xff0c;逾期汇总..... 这么多特征来做机器学习模型应该还不错。本次带来&…

板凳-------Mysql cookbook学习 (十二--------3_2)

3.3链接表 结构 P79页 用一个类图来表示EmployeeNode类的结构&#xff0c;展示其属性和关系&#xff1a; plaintext ----------------------------------------- | EmployeeNode | ----------------------------------------- | - emp_no: int …

深度学习图像预处理:统一输入图像尺寸方案

在实际训练中&#xff0c;最常见也最简单的做法&#xff0c;就是在送入网络前把所有图片「变形」到同一个分辨率&#xff08;比如 256256 或 224224&#xff09;&#xff0c;或者先裁剪&#xff0f;填充成同样大小。具体而言&#xff0c;可以分成以下几类方案&#xff1a;一、图…

pytest-log

问题1&#xff1a;我们在运行测试用例的时候如何记录测试的log&#xff0c;如何使用&#xff1f;问题2&#xff1a;我写的函数&#xff0c;为了方便log记录&#xff0c;但是在pytest运行时&#xff0c;会兼容pytest且不会重复记录&#xff0c;怎么解决&#xff1f;1、pytest有内…

在安卓源码中添加自定义jar包给源码中某些模块使用

一、具体步骤 1. 准备目录与 Jar 包 在vendor下 创建新的模块目录&#xff0c;放入demo.jar 包&#xff1a; demojar/ # 模块目录 ├── Android.bp # 编译配置文件 └── demo.jar 2. 编写 Android.bp 配置 Android.bp 示例配置&#xff1a; java_import {…

buntu 22.04 上离线安装Docker 25.0.5(二)

以下有免费的4090云主机提供ubuntu22.04系统的其他入门实践操作 地址&#xff1a;星宇科技 | GPU服务器 高性能云主机 云服务器-登录 相关兑换码星宇社区---4090算力卡免费体验、共享开发社区-CSDN博客 兑换码要是过期了&#xff0c;可以私信我获取最新兑换码&#xff01;&a…

初探 Web 环境下的 LLM 安全:攻击原理与风险边界

文章目录前言1 什么是大型语言模型&#xff08;LLM&#xff09;&#xff1f;1.1 LLM的核心特征1.2 LLM在Web场景中的典型应用2 LLM攻击的核心手段&#xff1a;提示注入与权限滥用3 LLM与API集成的安全隐患&#xff1a;工作流中的漏洞节点3.1 LLM-API集成的典型工作流3.2 工作流…

【新手向】PyTorch常用Tensor shape变换方法

【新手向】PyTorch常用Tensor shape变换方法 前言 B站UP主科研水神大队长的视频中介绍了“缝合模块”大法&#xff0c;其中专门强调了“深度学习 玩的就是shape”。受此启发&#xff0c;专门整理能够调整tensor形状的几个内置函数&#xff0c;方便以后更好地调整PyTorch代码中的…

React 18 vs Vue3:状态管理方案深度对比

🔥 背景: React有Redux、Zustand、Jotai等方案 Vue有Pinia、Vuex 4.x 如何选择适合项目的方案? 🔍 核心对比: 维度 React (Redux Toolkit) Vue3 (Pinia) 类型安全 ✅ 需手动配置TS ✅ 自动类型推导 代码量 较多(需写action) 较少(类似Vuex 5) 响应式原理 不可变数据…

UE5网络联机函数

Find Sessions Create Session Join Session Destroy Session Steam是p2p直接联机 一、steam提供的测试用AppId AppId是steam为每一款游戏所设定的独有标识&#xff0c;每一款要上架steam的游戏都会拥有独一无二的AppId。不过为了方便开发者测试&#xff0c;steam提供了游…

Spring Boot 监控:AOP vs Filter vs Java Agent

01前言 在 高并发 微服务 中&#xff0c; 传统 手动埋点&#xff08;System.currentTimeMillis()&#xff09;就像用体温计量火箭速度——代码侵入、重复劳动、维护爆炸。 下文是无侵入、高精度、全链路 监控 API 耗时&#xff0c;全程不碰业务代码的方案&#xff01; 02实战&…

基于Android的电子记账本系统

博主介绍&#xff1a;java高级开发&#xff0c;从事互联网行业多年&#xff0c;熟悉各种主流语言&#xff0c;精通java、python、php、爬虫、web开发&#xff0c;已经做了多年的毕业设计程序开发&#xff0c;开发过上千套毕业设计程序&#xff0c;没有什么华丽的语言&#xff0…

7月17日日记

结束了数学建模之后的这两天一直在紧张的复习&#xff0c;但是说实话效率有点低&#xff0c;因为可能觉得自己找到了两个小时速成课&#xff0c;觉得无所谓了&#xff0c;所以有点放松了。在宿舍杰哥和林雨城却一直在复习&#xff0c;感觉他们的微积分和线性代数复习的都比我好…

Linux下SPI设备驱动开发

一.SPI协议介绍1.硬件连接介绍引脚含义&#xff1a;DO(MOSI)&#xff1a;Master Output, Slave Input&#xff0c;SPI主控用来发出数据&#xff0c;SPI从设备用来接收数据。DI(MISO)&#xff1a;Master Input, Slave Output&#xff0c;SPI主控用来发出数据&#xff0c;SPI从设…

用Dify构建气象智能体:从0到1搭建AI工作流实战指南

作为一名Agent产品经理,我最近在负责气象智能体的建设项目。传统气象服务面临三大痛点:数据孤岛严重(气象局API、卫星云图、地面观测站等多源数据格式不一)、响应链路长(从数据采集到预警发布需人工介入多个环节)、交互体验单一(用户只能被动接收标准化预警,无法个性化…

Android NDK ffmpeg 音视频开发实战

文章目录接入FFmpeg1.下载FFmpeg 源码2.编译FFmpeg.so库异常处理3.自定义FFmpeg交互so库创建4.配置CMakeLists.txt5.CMakeLists.txt 环境配置6.Native与Java层调用解码器准备接入FFmpeg 1.下载FFmpeg 源码 FFmpeg官网地址 2.编译FFmpeg.so库 移动 FFmpeg 源码文件夹至 Andr…

使用 go-redis-entraid 实现 Entra ID 无密钥认证

1、依赖与安装 步骤命令说明安装&#xff08;或升级&#xff09; go-redis v9.9go get github.com/redis/go-redis/v9latestentraid 必须 ≥ 9.9.0安装 go-redis-entraidgo get github.com/redis/go-redis-entraid自动拉取 transit 依赖 2、认证方式一览 方式说明创建 Stream…

window上docker安装RabbitMQ

1、要进http://localhost:15672管理页面需要安装management版本2、搜索镜像并pull3、启动镜像时将端口映射出来4、启动成功&#xff0c;点击可查看日志详情&#xff0c;浏览器访问5、直接使用guest/guest登录会报错User can only log in via localhost解决办法有两个&#xff1…

异世界历险之数据结构世界(排序(插入,希尔,堆排))

前言 介绍 插入排序 基本知识&#xff1a; 直接插入排序是一种简单的插入排序法&#xff0c;其基本思想是&#xff1a; 把待排序的记录按其关键码值的大小逐个插入到一个已经排好序的有序序列中&#xff0c;直到所有的记录插入完为止&#xff0c;得到一个新的有序序列 直接插入…

oracle 数据库中,将几张表的数据按指定日期范围实时同步至同一个数据库的备份表中。

以下是一个Oracle数据库中实现表数据按指定日期范围实时同步至备份表的解决方案。这个方案使用存储过程和触发器组合实现&#xff1a; 1. 创建备份表结构 首先需要为每张需要备份的表创建对应的备份表&#xff0c;结构与原表相同&#xff1a; -- 为原表创建备份表&#xff08;示…