ViewModel原理分析

认识 ViewModel

ViewModel 是一种用来存储和管理UI相关数据的类。

ViewModel 的作用可以从两个方面去理解:

  • UI界面控制器:在最初的MVC模式中,由于 Activity / Fragment 承担的职责过重,因此在后续的 MVP、MVVM 模式中,选择将 Activity / Fragment 中与视图无关的职责抽离出来,在 MVP 模式中叫作 Presenter,在 MVVM 模式中叫作 ViewModel。使用 ViewModel 来承担界面控制器的职责,并且配合 LiveData / Flow 实现数据驱动。
  • 数据存储:由于 Activity 存在因配置变更销毁重建的机制,会造成 Activity 中的所有瞬态数据丢失,例如网络请求得到的用户信息、视频播放信息或者异步任务都会丢失。而 ViewModel 的特点是生命周期长于 Activity ,因此能够应对 Activity 因配置变更而重建的场景,在重建的过程中恢复 ViewModel 数据,从而降低用户体验受损。

ViewModel 生命周期示意图:
ViewModel 生命周期示意图

使用 ViewModel

使用步骤:

  1. 自定义 ViewModel 继承 ViewModel。
  2. 在自定义 ViewModel 中编写获取UI数据的逻辑。
  3. 配合使用 LiveData / Flow 实现数据驱动。
  4. 在 Activity / Fragment中 获取 ViewModel 实例。
  5. 监听或收集 ViewModel 中的 LiveData / Flow 数据,进行对应的UI更新。

简单示例:

class TestFlowViewModel : ViewModel() {private val _state: MutableStateFlow<Int> = MutableStateFlow(0)val state: StateFlow<Int> get() = _stateprivate val _live: MutableLiveData<String> = MutableLiveData<String>()val live: LiveData<String> get() = _livefun test() {_live.value = "1"for (state in 1..5) {viewModelScope.launch(Dispatchers.IO) {delay(100L * state)_state.emit(state)}}}
}

Activity / Fragment 中相关代码:

private val viewModel: TestFlowViewModel by viewModels()lifecycleScope.launch {launch {repeatOnLifecycle(Lifecycle.State.STARTED) {viewModel.state.collect {Log.d(TAG, "state: $it ")}}}delay(100)viewModel.test()
}
viewModel.live.observe(this) {Log.d(TAG, "it: $it")
}

在 Activity / Fragment中 获取 ViewModel 实例有两种方式,一种是通过ViewModelProvider获取,也key自定义ViewModelProvider.Factory,

private val viewModel = ViewModelProvider(this).get(TestFlowViewModel::class.java)

另一种就是上面示例里面使用的方式:使用 Kotlin by 委托属性,本质上是间接使用了 ViewModelProvider。

@MainThread
public inline fun <reified VM : ViewModel> ComponentActivity.viewModels(noinline extrasProducer: (() -> CreationExtras)? = null,noinline factoryProducer: (() -> Factory)? = null
): Lazy<VM> {val factoryPromise = factoryProducer ?: {defaultViewModelProviderFactory}return ViewModelLazy(VM::class,{ viewModelStore },factoryPromise,{ extrasProducer?.invoke() ?: this.defaultViewModelCreationExtras })
}public class ViewModelLazy<VM : ViewModel> @JvmOverloads constructor(private val viewModelClass: KClass<VM>,private val storeProducer: () -> ViewModelStore,private val factoryProducer: () -> ViewModelProvider.Factory,private val extrasProducer: () -> CreationExtras = { CreationExtras.Empty }
) : Lazy<VM> {private var cached: VM? = nulloverride val value: VMget() {val viewModel = cachedreturn if (viewModel == null) {val factory = factoryProducer()val store = storeProducer()ViewModelProvider(store,factory,extrasProducer()).get(viewModelClass.java).also {cached = it}} else {viewModel}}override fun isInitialized(): Boolean = cached != null
}

分析 ViewModel 原理

ViewModel 创建过程

上面说到了创建 ViewModel 实例的方法最终都是通过 ViewModelProvider 完成的。ViewModelProvider 可以理解为创建 ViewModel 的工具类,它需要 2 个参数:

  • ViewModelStoreOwner: 它对应于 Activity / Fragment 等持有 ViewModel 的宿主,它们内部通过 ViewModelStore 维持一个 ViewModel 的映射表,ViewModelStore 是实现 ViewModel 作用域和数据恢复的关键;
  • Factory: 它对应于 ViewModel 的创建工厂,缺省时将使用默认的 NewInstanceFactory 工厂来反射创建 ViewModel 实例。

创建 ViewModelProvider 工具类后,通过 get() 方法来获取 ViewModel 的实例。get() 方法内部首先会从 ViewModelStore 中取缓存,没有缓存才会通过 ViewModel 工厂创建实例再缓存到 ViewModelStore 中。

    @MainThreadpublic open operator fun <T : ViewModel> get(key: String, modelClass: Class<T>): T {// 先从 ViewModelStore 中取缓存val viewModel = store[key]// 存在 ViewModel,直接返回if (modelClass.isInstance(viewModel)) {(factory as? OnRequeryFactory)?.onRequery(viewModel!!)return viewModel as T} else {@Suppress("ControlFlowWithEmptyBody")if (viewModel != null) {// TODO: log a warning.}}val extras = MutableCreationExtras(defaultCreationExtras)extras[VIEW_MODEL_KEY] = key// AGP has some desugaring issues associated with compileOnly dependencies so we need to// fall back to the other create method to keep from crashing.// 不存在则使用 ViewModel 工厂创建实例,并放入 ViewModelStorereturn try {factory.create(modelClass, extras)} catch (e: AbstractMethodError) {factory.create(modelClass)}.also { store.put(key, it) }}

ViewModelStore 是 ViewModel 存储器,内部通过 LinkedHashMap 存储 ViewModel。

ViewModelStoreOwner 是一个接口,ViewModelStore 的持有者是ViewModelStoreOwner 的实现类,包括有ComponentActivity和Fragment,它们内部都保存着一个 ViewModelStore。

interface ViewModelStoreOwner {/*** The owned [ViewModelStore]*/val viewModelStore: ViewModelStore
}
public class ComponentActivity extends androidx.core.app.ComponentActivity implementsContextAware,LifecycleOwner,ViewModelStoreOwner ... {private ViewModelStore mViewModelStore;private ViewModelProvider.Factory mDefaultFactory;@NonNull@Overridepublic ViewModelStore getViewModelStore() {...ensureViewModelStore();return mViewModelStore;}
}

正因为 ViewModel 宿主内部都保存着一个 ViewModelStore ,因此在同一个宿主上重复调用 ViewModelProvider#get() 返回同一个 ViewModel 实例,这也就做到了 fragment 共享 activity 的ViewModel 实例以及 fragment 之间共享 ViewModel。

为什么 Activity 在屏幕旋转重建后可以恢复 ViewModel?

上面说到 ViewModel 其实是被保存在 ViewModelStore 里,所以 Activity 在屏幕旋转重建后恢复 ViewModel 其实是重新获取到了原有的 ViewModelStore。那么 Activity 里的 ViewModelStore 究竟是怎么生成的呢?

ViewModelStore 的生成

ComponentActivity 在构造函数中设置了对 lifecycle 的监听,当 activity 的生命周期变化时会调用ensureViewModelStore方法,确保 ViewModelStore 的存在。

        getLifecycle().addObserver(new LifecycleEventObserver() {@Overridepublic void onStateChanged(@NonNull LifecycleOwner source,@NonNull Lifecycle.Event event) {ensureViewModelStore();getLifecycle().removeObserver(this);}});
    @SuppressWarnings("WeakerAccess") /* synthetic access */void ensureViewModelStore() {if (mViewModelStore == null) {NonConfigurationInstances nc =(NonConfigurationInstances) getLastNonConfigurationInstance();if (nc != null) {// Restore the ViewModelStore from NonConfigurationInstancesmViewModelStore = nc.viewModelStore;}if (mViewModelStore == null) {mViewModelStore = new ViewModelStore();}}}

当 ViewModelStore 不存在时,ensureViewModelStore 方法会进行 ViewModelStore 的生成,首先通过 getLastNonConfigurationInstance 获取到 NonConfigurationInstances,并从中获取 ViewModelStore,若 ViewModelStore 仍未空,则创建 new 一个新的。

看到这,知道了 ViewModelStore 的生成来源于两处,一处为 NonConfigurationInstances 中获取,另一处是新创建。那么 Activity 在屏幕旋转重建后获取到了原有的 ViewModelStore 是不是就是从 NonConfigurationInstances 中获取的呢?

接下去看一下 Activity 在屏幕旋转重建后,ViewModelStore 都干什么去了呢?

Activity 因配置变更而重建时(比如屏幕旋转),可以将页面上的数据或状态可以定义为 2 类:

  • 配置数据:例如窗口大小、多语言字符、多主题资源等,当设备配置变更时,需要根据最新的配置重新读取新的数据,因此这部分数据在配置变更后便失去意义,自然也就没有存在的价值;
  • 非配置数据:例如用户信息、视频播放信息、异步任务等非配置相关数据,这些数据跟设备配置没有一点关系,如果在重建 Activity 的过程中丢失,不仅没有必要,而且会损失用户体验(无法快速恢复页面数据,或者丢失页面进度)。
    基于以上考虑,Activity 是支持在设备配置变更重建时恢复非配置数据的,源码中存在 NonConfiguration 字眼的代码,就是与这个机制相关的代码。

当 Activity 因配置变更而重建时,ActivityThreadhandleRelaunchActivity 方法会执行,先 handleDestroyActivity 销毁 Activity,然后 handleLaunchActivity 重建 Activity。

Activity 销毁过程

在 handleDestroyActivity 方法里执行到 performDestroyActivity 时,会执行 activity 的 retainNonConfigurationInstances 方法,将非配置数据临时存储在当前 Activity 的 ActivityClientRecord(当前进程内存)。

    void performDestroyActivity(ActivityClientRecord r, boolean finishing,int configChanges, boolean getNonConfigInstance, String reason) {Class<? extends Activity> activityClass = null;if (localLOGV) Slog.v(TAG, "Performing finish of " + r);activityClass = r.activity.getClass();r.activity.mConfigChangeFlags |= configChanges;if (finishing) {r.activity.mFinished = true;}performPauseActivityIfNeeded(r, "destroy");if (!r.stopped) {callActivityOnStop(r, false /* saveState */, "destroy");}if (getNonConfigInstance) {try {// 将非配置数据临时存储在 ActivityClientRecordr.lastNonConfigurationInstances = r.activity.retainNonConfigurationInstances();} catch (Exception e) {if (!mInstrumentation.onException(r.activity, e)) {throw new RuntimeException("Unable to retain activity "+ r.intent.getComponent().toShortString() + ": " + e.toString(), e);}}}...}
// 获取 Activity 的非配置相关数据
NonConfigurationInstances retainNonConfigurationInstances() {// 构造 Activity 级别的非配置数据Object activity = onRetainNonConfigurationInstance();...// 构造 Fragment 级别的费配置数据数据FragmentManagerNonConfig fragments = mFragments.retainNestedNonConfig();...// 构造并返回 NonConfigurationInstances 非配置相关数据类NonConfigurationInstances nci = new NonConfigurationInstances();nci.activity = activity;nci.fragments = fragments;...return nci;
}// 默认返回 null,由 Activity 子类定义
public Object onRetainNonConfigurationInstance() {return null;
}

看一下onRetainNonConfigurationInstance在 ComponentActivity 中的实现:

    public final Object onRetainNonConfigurationInstance() {// Maintain backward compatibility.Object custom = onRetainCustomNonConfigurationInstance();ViewModelStore viewModelStore = mViewModelStore;if (viewModelStore == null) {// No one called getViewModelStore(), so see if there was an existing// ViewModelStore from our last NonConfigurationInstance// 这一个 if 语句是处理异常边界情况: // 如果重建的 Activity 没有调用 getViewModelStore(),那么旧的 Activity 中的 ViewModel 并没有被取出来, // 因此在准备再一次存储当前 Activity 时,需要检查一下旧 Activity 传过来的数据。NonConfigurationInstances nc =(NonConfigurationInstances) getLastNonConfigurationInstance();if (nc != null) {viewModelStore = nc.viewModelStore;}}// ViewModelStore 为空说明当前 Activity 和旧 Activity 都没有 ViewModel,没必要存储和恢复if (viewModelStore == null && custom == null) {return null;}NonConfigurationInstances nci = new NonConfigurationInstances();nci.custom = custom;// 保存 ViewModelStore 对象nci.viewModelStore = viewModelStore;return nci;}

看到了 ViewModelStore 实例的保存,就这样,当 Activity 被销毁时,ViewModelStore 实例被保存进了 NonConfigurationInstances 中,进而被临时存储在了 ActivityClientRecord 里。

Activity 重建过程

在 handleLaunchActivity 方法里执行到 performLaunchActivity 时,会执行 activity 的attach方法,并将 ActivityClientRecord中的 NonConfigurationInstances 传入。

// 在 Activity#attach() 中传递旧 Activity 的数据
NonConfigurationInstances mLastNonConfigurationInstances;final void attach(Context context, ActivityThread aThread,...NonConfigurationInstances lastNonConfigurationInstances) {...mLastNonConfigurationInstances = lastNonConfigurationInstances;...
}

至此,旧 Activity 的数据就传递到新 Activity 的成员变量 mLastNonConfigurationInstances 中了,ViewModelStore 将从 mLastNonConfigurationInstances 中获取。

ViewModel 数据清除

ViewModel 的数据又是在什么时候会被清除呢?

ViewModel 中有一个 clear 方法用于数据清除,在 ViewModelStore#clear() 方法中被调用。

上面提到 ViewModelStore 的生成是 ComponentActivity 在构造函数中设置了对 lifecycle 的监听,当 activity 的生命周期变化时会调用ensureViewModelStore方法,确保 ViewModelStore 的存在。
ComponentActivity 在构造函数中设置对 lifecycle 的监听可不只有这一处,ViewModel 数据的清除也是通过对 lifecycle 的监听,当 Activity 进入 destroyed 状态,并且 Activity 不处于配置变更重建的阶段,将调用 ViewModelStore#clear() 清除 ViewModel 数据。

        getLifecycle().addObserver(new LifecycleEventObserver() {@Overridepublic void onStateChanged(@NonNull LifecycleOwner source,@NonNull Lifecycle.Event event) {if (event == Lifecycle.Event.ON_DESTROY) {// Clear out the available contextmContextAwareHelper.clearAvailableContext();// And clear the ViewModelStoreif (!isChangingConfigurations()) {getViewModelStore().clear();}}}});

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

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

相关文章

【C++进阶】模板与仿函数:C++编程中的泛型与函数式编程思想

&#x1f4dd;个人主页&#x1f339;&#xff1a;Eternity._ ⏩收录专栏⏪&#xff1a;C “ 登神长阶 ” &#x1f921;往期回顾&#x1f921;&#xff1a;栈和队列相关知识 &#x1f339;&#x1f339;期待您的关注 &#x1f339;&#x1f339; ❀模板进阶 &#x1f9e9;<&…

OpenGauss数据库-8.权限管理

第2关&#xff1a;权限设置 gsql -d postgres -U gaussdb -W passwd123123 CREATE ROLE lily WITH CREATEDB PASSWORD passwd123123; GRANT lily TO gaussdb; 第3关&#xff1a;管理员 gsql -d postgres -U gaussdb -W passwd123123 CREATE USER peter WITH SYSADMIN PASSWOR…

uniapp地图选择位置

直接上代码 通过一个点击事件调用官方api即可调用 点击调用成功后显示如下 然后选择自己所需要的位置即可

解读光纤模块的参数有哪些

光模块的具体参数有传输速率、传输距离、中心波长、光纤类型、光口类型、工作温度范围、最大功耗等。下面给大家详解一下各个参数的作用 因为光纤本身对光信号有色散、损耗等副作用。因此不同类型的光源发出的光所能传输的距离不一样。对接光接口时&#xff0c;应根据最远的信号…

AutoKG:为语言模型打造高效自动化知识图谱

在人工智能领域&#xff0c;大型语言模型&#xff08;LLMs&#xff09;如BERT、RoBERTa、T5和PaLM等&#xff0c;以其在自然语言处理&#xff08;NLP&#xff09;任务中的卓越性能而著称。然而&#xff0c;这些模型在提供信息时可能会产生“幻觉”&#xff0c;即提供看似合理但…

Vue 路由传递参数 query、params

1、to的对象写法,绑定参数 <template> 2 <ul> 3 <li v-for"m in messlist" :key"m.id"> 4 <router-link :to"{ //使用params时&#xff0c;这个路径必须用name及别名......name: xiangqing, path: /bbb/message/deta…

Python酷库之旅-比翼双飞情侣库(01)

目录 一、xlrd库的由来 二、xlrd库优缺点 1、优点 1-1、支持多种Excel文件格式 1-2、高效性 1-3、开源性 1-4、简单易用 1-5、良好的兼容性 2、缺点 2-1、对.xlsx格式支持有限 2-2、功能相对单一 2-3、更新和维护频率低 2-4、依赖外部资源 三、xlrd库的版本说明 …

如何格式化SQL语句(以MySQL和SQLynx为例)

目录 1 SQLynx 格式化MySQL的SQL 语句功能介绍 1.1 主要特点 1.2 使用步骤 1.3 操作示例 2 结论 SQLynx 是一款功能强大的 SQL 集成开发环境&#xff08;IDE&#xff09;&#xff0c;它提供了多种工具和功能来提高用户的生产力和代码质量。其中&#xff0c;SQL 语句的格式…

Mybatis Log Free

安装后重启 在 application.yml 配置 configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl 选择效果

Java---BigInteger和BigDecimal和枚举

1.简介 1.BigInteger可以支持任意长度的整数 2.BigDecimal可以支持任意精度的浮点数 3.用来做精确计算 2.创建方式 new BigInteger(); new BigInteger(参数1,进制)&#xff1a;可以将不同进制转成10进制显示 new BigDecimal(); BigInteger.valueOf(); BigDecimal.valueOf();…

vue30:props详解

1&#xff1a;props类型校验&#xff1a; 2&#xff1a;props类型自定义校验&#xff1a; 3:子组件不能通过prop直接修改父组件的数据

阿里云物联网平台案例教程

1、定义&#xff1a; ​ 物联网&#xff08;简称IOT&#xff09;把任何物体与物联网相连接&#xff0c;进行消息的交换和通信&#xff0c;实现对物品的智能化识别。简单说是&#xff1a;物联网就是把所有的物体连接起来相互作用&#xff0c;形成一个互联互通的网络&#xff0c…

[发布]嵌入式系统远程测控软件-基于Qt

目录 一. 引言二. 软件功能2.1 原理2.2 软件功能2.3 运行环境 三. 软件操作使用3.1 软件界面3.2 软件功能使用详解3.2.1 连接3.2.2 数据监测&#xff08;串口示波器&#xff09;3.2.3 数据修改3.2.4 数据保存 3.3 软件的硬件连接 四. 通信协议——STM32移植篇4.1 通信协议4.2 S…

提升易用性,OceanBase生态管控产品的“从小到大”

2022年&#xff0c;OceanBase发布4.0版本“小鱼”&#xff0c;并首次公开提出了单机分布式一体化这一理念&#xff0c;旨在适应大小不同规模的工作负载&#xff0c;全面满足用户数据库“从小到大”全生命周期的需求。当时&#xff0c;我们所说的“从小到大”主要聚焦于数据库的…

JVM产生FullGC的原因有哪些?

JVM产生FullGC的原因有哪些&#xff1f; 在Java虚拟机&#xff08;JVM&#xff09;中&#xff0c;垃圾回收&#xff08;Garbage Collection&#xff0c;简称GC&#xff09;是一个非常重要的机制。GC的目的是自动管理内存&#xff0c;回收不再使用的对象&#xff0c;防止内存泄…

数据合规怎么做?哪些机构可以做数据合规

企业将数据资源入表的工作是一项复杂而全面的任务 财务部门负责统计数据资源的成本、销售数据等信息,并确保数据资源的会计处理符合会计要求&#xff1b; 数据部门则负责统计数据成本来源、价值实现路径等信息&#xff1b; 法务部门需要确认数据的收集和使用遵循相关的合规要求…

UE5 Sequencer 使用指导 - 学习笔记

https://www.bilibili.com/video/BV1jG411L7r7/?spm_id_from333.337.search-card.all.click&vd_source707ec8983cc32e6e065d5496a7f79ee6 Sequencer 01 1.1 调整视口 调整窗口数量 调整视口类型为Cinematic视口 视口显示网格&#xff0c;或者条件参考线 1.2 关卡动画与…

Redis链表

Redis链表 C语言没有内置链表&#xff0c;Redis自己构建的链表 链表在redis中的实现 typedef struct list {//表头节点listNode *head;//表尾节点listNode *tail;//节点数量unsigned long len;//节点值复制函数void *(*dup) (void *ptr);//节点值释放函数void (*free) (void …

指定cuda版本的torch包安装

文章目录 1.查看自己电脑的cuda版本2.确定安装torch的conda指令2.1进入网站[cuda对应的torch版本](https://pytorch.org/get-started/previous-versions/) 3.检验torch的cuda版本是否可用 1.查看自己电脑的cuda版本 winr输入cmd回车进行电脑终端界面 输入nvidia-smi指令 nvid…

【智能家居控制系统项目】一、项目系统镜像烧录与系统登录

前言 完成本章节将可以获得本项目的系统UI界面功能。本章节主要介绍如何烧录项目系统镜像以及进入系统。配套的视频介绍可以点击跳转到智能家居项目复刻配套视频 1.系统功能页面介绍 完成本章全部步骤&#xff0c;我们将可使用以下项目系统功能界面。 1.1 家居总览界面 主界面…