当前位置: 首页 > ai >正文

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.xdnf.cn/news/14947.html

相关文章:

  • 深度学习 必然用到的 微积分知识
  • 整合Spring、Spring MVC与MyBatis:构建高效Java Web应用
  • 【实习篇】之Http头部字段之Disposition介绍
  • vue快速上手
  • 解决IDEA缺少Add Framework Support选项的可行性方案
  • 跨平台ROS2视觉数据流:服务器运行IsaacSim+Foxglove本地可视化全攻略
  • 单片机STM32F103:DMA的原理以及应用
  • Python通关秘籍之基础教程(一)
  • 供应链管理-采购:谈判方式、理念、技巧
  • 【C++】第四章—— 函数重载 Function Overloading 笔记
  • android activity生命周期温习
  • JSP数据交互
  • JAVA如何实现Redis同步
  • 软件发布的完整流程梳理
  • 每日mysql
  • Debezium:一款基于CDC的开源数据同步工具
  • 如何使用Pytest进行测试?
  • Ubuntu22.04 设置显示存在双屏却无法双屏显示
  • MS32C001-C单片机,32位ARM M0+内核,宽电压、低功耗、小封装。
  • 【图像处理基石】如何检测到画面中的ppt并对其进行增强?
  • 【问题思考总结】两个向量之和的二范数公式是什么?
  • Shell 脚本0基础教学(一)
  • 景观桥 涵洞 城门等遮挡物对汽车安全性的影响数学建模和计算方法,需要收集那些数据
  • Windows Subsystem for Linux (WSL):现代开发的终极跨平台方案
  • 专题一_双指针_有效三角形的个数
  • 【Linux | 网络】socket编程 - 使用TCP实现服务端向客户端提供简单的服务
  • 通过Tcl脚本命令:set_param labtools.auto_update_hardware 0
  • Spring Cloud服务注册与发现:架构设计与技术实践深度分析
  • VS Code侧边栏的“资源管理器找不到解决办法“、VScode重置视图位置/还原默认视图位置
  • Linux建立本地软件仓库