Android ViewModel机制与底层原理详解

Android 的 ViewModel 是 Jetpack 架构组件库的核心部分,旨在以生命周期感知的方式存储和管理与 UI 相关的数据。它的核心目标是解决两大痛点:

  1. 数据持久化: 在配置变更(如屏幕旋转、语言切换、多窗口模式切换)时保留数据,避免重新加载数据造成的资源浪费和用户体验中断。
  2. 职责分离: 将 UI 控制器(Activity/Fragment)与数据操作逻辑分离,使代码更清晰、可测试性更强。

核心机制与原理详解

  1. 设计目标与核心思想:

    • 生命周期感知: ViewModel 对象的生命周期比其关联的 UI 控制器(Activity/Fragment)更长。它从 UI 控制器创建开始,直到其关联的 UI 控制器永久销毁Activity finish()Fragment 分离且不再附加)时才被销毁。这意味着配置变更导致的临时销毁与重建不会影响 ViewModel
    • 数据持有者: 主要负责持有、准备和管理 UI 所需的数据。它可以执行数据获取(如从数据库、网络)、转换、聚合等操作。
    • UI 控制器解耦: UI 控制器(Activity/Fragment)负责显示数据和响应用户交互,ViewModel 负责提供数据和处理业务逻辑。两者通过观察(如 LiveData)或直接调用接口进行通信。
  2. 创建过程:

    • 入口点: 通常使用 ViewModelProvider 来获取 ViewModel 实例。
    // 在 Activity 中
    val viewModel = ViewModelProvider(this).get(MyViewModel::class.java)
    // 在 Fragment 中 (推荐使用 activityViewModels 或 viewModels)
    val viewModel: MyViewModel by viewModels()
    val sharedViewModel: SharedViewModel by activityViewModels()
    
    • 关键参数 - ViewModelStoreOwner: ViewModelProvider 的构造函数需要一个 ViewModelStoreOwnerComponentActivity (AppCompatActivity 的基类) 和 Fragment 都实现了这个接口。它提供了访问 ViewModelStore 的能力。
    • ViewModelProvider 的作用:
      • 检查 ViewModelStore 中是否已存在请求类型的 ViewModel 实例。
      • 如果存在,直接返回该实例。
      • 如果不存在,则通过 Factory(默认为 NewInstanceFactoryAndroidViewModelFactory)创建新实例,并将其存储在 ViewModelStore 中,然后返回。
  3. 存储与作用域 - ViewModelStore

    • 核心容器: 每个 ViewModelStoreOwner(如一个 Activity 或一个特定 Fragment)内部都维护着一个 ViewModelStore。它是一个简单的容器类(通常是 HashMap<String, ViewModel>),负责存储与该作用域关联的所有 ViewModel 实例。
    • 键 (Key): ViewModelViewModelStore 中的存储键通常是其类名(如 "com.example.MyViewModel")。当使用带 Factory 的特定键时(如为同一类型创建多个实例),键会更复杂。
    • 配置变更下的存活: 当配置变更发生时,系统销毁并重建 UI 控制器 (Activity/Fragment)。但是,系统会将 ViewModelStore 对象保留在内存中。重建后的新 UI 控制器实例会重新附加到同一个 ViewModelStore。因此,ViewModelProvider 能在新 UI 控制器中检索到之前创建的 ViewModel 实例。
    • 永久销毁: 当 UI 控制器真正结束其生命周期(用户按返回键、调用 finish()Fragment 被永久移除),系统会调用 ViewModelStoreclear() 方法。该方法会遍历所有存储的 ViewModel 实例,调用它们的 onCleared() 方法(用于释放资源,如取消异步任务),然后清空 Map。之后,ViewModelStore 及其包含的 ViewModel 实例会被垃圾回收。
  4. 与生命周期的绑定 - Lifecycle

    • 自动关联: 当你通过 ViewModelProvider(owner) 创建 ViewModel 时,该 ViewModel 就自动与 owner (ViewModelStoreOwner) 的生命周期关联起来了。
    • onCleared() 钩子: ViewModel 类提供了一个 onCleared() 方法。当关联的 ViewModelStoreclear() 时(即 UI 控制器永久销毁时),框架会自动调用这个方法。开发者可以重写此方法来清理资源(如取消正在进行的网络请求、关闭数据库连接、移除监听器等)。这是 ViewModel 感知其“结束”生命周期的关键点。
  5. 数据通信 (通常结合 LiveData):

    • 最佳搭档: 虽然 ViewModel 可以包含任何数据,但 LiveData 是其推荐的用于向 UI 暴露数据的方式。
    • 机制: ViewModel 内部持有 LiveData 对象(通常是 MutableLiveData 私有,暴露为 LiveData 公有)。UI 控制器 (Activity/Fragment) 在 onCreate()onViewCreated() 中观察这些 LiveData
    • 优势:
      • 生命周期感知订阅: LiveData 自动管理订阅,确保只在 UI 控制器处于活跃状态 (STARTEDRESUMED) 时才更新 UI,避免在后台更新导致的崩溃或资源浪费。
      • 数据更新: ViewModel 中的业务逻辑(如响应按钮点击的网络请求)完成后,通过更新 MutableLiveData 的值来触发 LiveData 通知观察者(UI 控制器)。
      • 配置变更无缝衔接: 由于 ViewModelLiveData 在配置变更后存活,新的 UI 控制器重新观察同一个 LiveData 时,会立即收到最后一次保存的数据,从而实现无缝恢复。
  6. 作用域扩展 (SavedStateHandle):

    • 需求: 基本 ViewModel 在进程被系统杀死后重建时,其内部数据也会丢失。需要一种机制在进程死亡后恢复少量关键 UI 状态(如列表滚动位置、输入框临时内容)。
    • 解决方案: ViewModel 库提供了 SavedStateHandle 作为 ViewModel 构造函数的参数。
    • 原理:
      • 当使用 SavedStateHandle 时,ViewModel 的创建工厂(如 AbstractSavedStateViewModelFactory)会负责将 SavedStateHandle 注入到 ViewModel 中。
      • SavedStateHandle 本质上是一个键值对容器 (Map<String, Any?>)。它利用底层 ActivityonSaveInstanceState(Bundle) 机制。
      • 在 UI 控制器临时销毁(配置变更)或可能永久销毁(进程回收)前,SavedStateHandle 中的数据会被序列化到 Bundle 中。
      • 在 UI 控制器重建后,Bundle 中的数据会被反序列化回 SavedStateHandle。这样,即使在进程被杀死后重建,ViewModel 也能通过 SavedStateHandle 恢复那些关键状态。
    • 使用:
      class MyViewModel(private val savedStateHandle: SavedStateHandle) : ViewModel() {val someState: MutableStateFlow<String> = savedStateHandle.getStateFlow("key", "")// 或者使用 LiveDataval liveDataState: LiveData<String> = savedStateHandle.getLiveData("key")fun updateState(newValue: String) {savedStateHandle["key"] = newValue // 自动触发保存}
      }
      // 创建时需要使用 SavedStateViewModelFactory 或 by viewModels() 自动处理
      
  7. Fragment 间共享数据:

    • 场景: 同一个 Activity 下的多个 Fragment 需要共享数据(如购物车、用户资料)。
    • 实现: 让这些 Fragment 使用 同一个作用域ViewModelStoreOwner。通常,这个共享的作用域就是它们所属的 Activity
    • 方法:Fragment 中,使用 activityViewModels() 委托或 ViewModelProvider(requireActivity()) 来获取 ViewModel 实例。
    • 原理: 所有通过该 Activity 作用域 (ViewModelStoreOwner) 获取的同一类型的 ViewModel(使用默认 Key),返回的都是同一个实例。因此,不同的 Fragment 访问的是同一个 ViewModel 对象,自然就实现了数据共享和通信。
  8. 底层关键类与交互:

    • ViewModel: 开发者继承的基类,包含数据和逻辑,有 onCleared() 钩子。
    • ViewModelStoreOwner: 接口(ComponentActivity, Fragment 实现),提供 getViewModelStore() 方法。
    • ViewModelStore: 内部维护一个 Map<String, ViewModel>,负责存储和清理 ViewModel
    • ViewModelProvider: 工厂类,负责从 ViewModelStore 获取或创建 ViewModel 实例。
    • ViewModelProvider.Factory: 接口,用于创建 ViewModel 实例(支持带参数构造函数)。
    • SavedStateHandle: 用于在进程死亡后恢复少量状态的辅助类。
    • ComponentActivity: 实现了 ViewModelStoreOwnerHasDefaultViewModelProviderFactory,在其 onRetainNonConfigurationInstance() 中保存 ViewModelStore,在 onCreate() 中恢复。在其 onDestroy() 中判断是否为配置变更决定是否调用 ViewModelStore.clear()
    • FragmentManagerViewModel: (Fragment 作用域实现的关键) 一个特殊的 ViewModel,由 FragmentManager 持有,用于管理 Fragment 作用域的 ViewModelStore 以及嵌套 Fragment 的作用域关系。
  9. 重要注意事项:

    • 绝不持有 View/Activity Context 引用: ViewModel 生命周期可能比 Activity 长。如果持有 Activity 引用,会导致 Activity 无法被回收,造成内存泄漏。如果需要 Application Context,使用 AndroidViewModel(它持有 Application 引用,Application 生命周期等同于进程)。
    • 轻量级状态恢复: SavedStateHandle 用于恢复少量、序列化/反序列化快的 UI 相关状态。不要用它存储大量数据或复杂对象。大数据应持久化到数据库或网络。
    • 异步操作:ViewModel 中启动的异步操作(如协程、LiveData 转换),必须在 onCleared() 中取消或清理,防止内存泄漏和无效更新。
    • 测试友好: 由于 ViewModel 不依赖 Android 框架的具体 UI,它们可以非常方便地在 JUnit 测试中进行单元测试。

总结流程图

(配置变更 / 进程重建)|v
+-------------------+
| UI Controller      | (Activity/Fragment) 销毁或重建
| (onDestroy)       | --(永久销毁?)--> Yes -> [调用 ViewModelStore.clear()] -> [触发 ViewModel.onCleared()]
|                   | --(配置变更?)--> Yes -> [系统保留 ViewModelStore]
+-------------------+|| (重建后)v
+-------------------+
| UI Controller      | (新的 Activity/Fragment 实例)
| (onCreate)        | --[创建 ViewModelProvider] --> [请求 ViewModel]
+-------------------+|v
+-------------------+
| ViewModelProvider | --[检查 ViewModelStore] --> [存在?] -> Yes -> 返回现有实例
|                   |                          --> No  -> [使用 Factory 创建新实例] -> [存入 ViewModelStore] -> 返回实例
+-------------------+|v
+-------------------+
| ViewModel         | --[持有 LiveData/SavedStateHandle] --> [提供数据/处理逻辑]
|                   | <--[观察 LiveData / 调用方法]------- UI Controller
+-------------------+|v
(UI 显示数据/响应用户交互)

ViewModel 的核心在于 ViewModelStore 在配置变更中的持久性,以及其生命周期与 UI 控制器的解耦(存活至永久销毁)。结合 LiveData 的生命周期感知数据观察和 SavedStateHandle 的轻量级状态持久化,它构成了 Android 现代、健壮、可测试的 UI 架构基石。理解 ViewModelStoreOwnerViewModelStoreViewModelProvider 的协作机制是掌握其底层原理的关键。

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

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

相关文章

双倍硬件=双倍性能?TDengine线性扩展能力深度实测验证!

软件扩展能力是软件架构设计中的一个关键要素&#xff0c;具有良好扩展能力的软件能够充分利用新增的硬件资源。当软件性能与硬件增加保持同步比例增长时&#xff0c;我们称这种现象为软件具有线性扩展能力。要实现这种线性扩展并不简单&#xff0c;它要求软件架构精心设计&…

频繁迭代下完成iOS App应用上架App Store:一次快速交付项目的完整回顾

在一次面向商户的会员系统App开发中&#xff0c;客户要求每周至少更新一次版本&#xff0c;涉及功能迭代、UI微调和部分支付方案的更新。团队使用Flutter进行跨平台开发&#xff0c;但大部分成员日常都在Windows或Linux环境&#xff0c;只有一台云Mac用于打包。如何在高频率发布…

springsecurity03--异常拦截处理(认证异常、权限异常)

目录 Spingsecurity异常拦截处理 认证异常拦截 权限异常拦截 注册异常拦截器 设置跨域访问 Spingsecurity异常拦截处理 认证异常拦截 /*自定义认证异常处理器类*/ Component public class MyAuthenticationExceptionHandler implements AuthenticationEntryPoint {Overr…

企业如何制作网站?网站制作的步骤与流程?

以下是2025年网站制作的综合指南&#xff0c;涵盖核心概念、主流技术及实施流程&#xff1a; 一、定义与范畴 网站制作是通过页面结构设计、程序设计、数据库开发等技术&#xff0c;将视觉设计转化为可交互网页的过程&#xff0c;包含前端展示与后台功能实现。其核心目标是为企…

Rust+Blender:打造高性能游戏引擎

基于Rust和Blender的游戏引擎 以下是基于Rust和Blender的游戏引擎开发实例,涵盖不同应用场景和技术方向的实际案例。案例分为工具链整合、渲染技术、物理模拟等类别,每个案例附核心代码片段或实现逻辑。 工具链整合案例 案例1:Blender模型导出到Bevy引擎 使用blender-bev…

Git基本操作1

Git 是一款分布式版本控制系统&#xff0c;主要用于高效管理代码版本和团队协作开发。它能精确记录每次代码修改&#xff0c;支持版本回溯和分支管理&#xff0c;让开发者可以并行工作而互不干扰。通过本地提交和远程仓库同步&#xff0c;Git 既保障了代码安全&#xff0c;又实…

React Native 组件间通信方式详解

React Native 组件间通信方式详解 在 React Native 开发中&#xff0c;组件间通信是核心概念之一。以下是几种主要的组件通信方式及其适用场景&#xff1a; 简单父子通信&#xff1a;使用 props 和回调函数兄弟组件通信&#xff1a;提升状态到共同父组件跨多级组件&#xff1a;…

TCP的可靠传输机制

TCP通过校验和、序列号、确认应答、重发控制、连接管理以及窗口控制等机制实现可靠性的传输。 先来看第一个可靠性传输的方法。 通过序列号和可靠性提供可靠性 TCP是面向字节的。TCP把应用层交下来的报文&#xff08;可能要划分为许多较短的报文段&#xff09;看成一个一个字节…

没有DBA的敏捷开发管理

前言一家人除了我都去旅游了&#xff0c;我这项请假&#xff0c;请不动啊。既然在家了&#xff0c;闲着也是闲着&#xff0c;就复盘下最近的工作&#xff0c;今天就复盘表结构管理吧&#xff0c;随系统启动的&#xff0c;不是flyway&#xff0c;而是另一个liquibase&#xff0c…

go-carbon v2.6.10发布,轻量级、语义化、对开发者友好的 golang 时间处理库

carbon 是一个轻量级、语义化、对开发者友好的 Golang 时间处理库&#xff0c;提供了对时间穿越、时间差值、时间极值、时间判断、星座、星座、农历、儒略日 / 简化儒略日、波斯历 / 伊朗历的支持。 carbon 目前已捐赠给 dromara 开源组织&#xff0c;已被 awesome-go 收录&am…

【AI News | 20250708】每日AI进展

AI Repos 1、claude-code-templates Claude Code Templates是一款全面的命令行工具&#xff0c;旨在为不同编程语言和框架&#xff08;如JavaScript/TypeScript、Python等&#xff0c;Go和Rust即将推出&#xff09;提供优化的Claude Code配置。它通过交互式设置、自动化钩子&a…

Nginx源码安装+静态站点部署指南(CentOS 7)

安装包&#xff1a;可自行前往我的飞书下载 Docs 也可以进入 nginx 官网&#xff0c;下载自己所需适应版本 nginx 开始安装nginx 1. 创建准备目录 cd /opt mkdir soft module # 创建软件包和源码解压目录 2. 安装依赖环境 yum -y install make zlib zlib-devel gcc-c l…

交换机的核心原理和作用

一、交换机的核心原理交换机是一种用于连接多台设备的网络硬件&#xff0c;其核心原理基于二层网络&#xff08;数据链路层&#xff09;的 MAC 地址寻址1. MAC 地址学习与存储当交换机接收到数据帧时&#xff0c;会读取帧中的源 MAC 地址&#xff0c;并将该地址与对应的端口号记…

【工具变量】上市公司企业金融强监管数据、资管新规数据(2001-2024年)

数据简介&#xff1a;参考顶刊《经济研究》李青原&#xff08;2022&#xff09;老师的做法&#xff0c;Post 为时间虚拟变量&#xff0c;根据资管新规实施的时间&#xff0c;当观测期为2018 年上半年及之后时&#xff0c;Post 取值1&#xff0c;否则取值0。PreFin 为资管新规实…

CSS Grid与Flexbox布局实战对比

概述 CSS布局技术在过去几年经历了重大变革&#xff0c;从传统的基于浮动和定位的方法&#xff0c;到现在强大的Flexbox和Grid布局系统。这两种现代布局方法极大地简化了复杂界面的开发过程&#xff0c;但它们各自适用于不同的场景。本文将对Flexbox和Grid进行深入比较&#x…

[Pytest][Part 4]多种测试运行方式

实现需求2&#xff1a;有两种运行测试的方式&#xff1a;通过config配置文件运行&#xff0c;测试只需要修改config配置文件cmdline 运行这里是新建一个config类来存储所有的测试配置&#xff0c;以后配置有修改的话也只需要修改这个类。根据目前的测试需求&#xff0c;config中…

平衡二叉树的删除操作

对于平衡二叉树的操作应对与考试只需要模拟出过程即可&#xff0c;且他的过程和插入的平衡方法一样&#xff0c;不一样的只是对于平衡因子的计算上。接下来我将给出方法①删除结点&#xff08;方法同“二叉排序树”&#xff09; ②一路向北找到最小不平衡子树&#xff0c;找不到…

Spark 4.0的 VariantType 类型优点以及使用分析

背景 本文基于Spark 4.0。 总结 对于半结构化的数据来说&#xff0c;我们一般会有两种方式进行存储: 第一种是存储为JSON String,这种可以保证Schema free&#xff0c;但是在使用的时候得解析为JSON&#xff0c;从而进行运算操作。 第二种是存储为Struct类型&#xff0c;这种虽…

17-C#封装,继承,多态与重载

C#封装继承多态 1. 2. 3.多态 public abstract class animal//抽象类 {public abstract void eat();//抽象方法 } public class cat : animal//继承 {public override void eat()//重写{messagebox.show("cat eat");} } public class dog: animal//继承 {public over…

恒创科技:香港站群服务器做seo站群优化效果如何

香港站群服务器做 SEO 站群优化效果如何?在当前搜索引擎优化竞争日益激烈的环境下&#xff0c;越来越多的企业开始关注站群策略这一高效的 SEO 手段。作为亚洲重要的网络枢纽&#xff0c;香港站群服务器因其独特优势&#xff0c;正成为实施 SEO 站群优化的热门选择。本文将客观…