Android Jetpack组件架构:ViewModel的原理

Android Jetpack组件架构:ViewModel的原理

在这里插入图片描述

导言

本篇文章是关于介绍ViewModel的,由于ViewModel的使用还是挺简单的,这里就不再介绍其的基本应用,我们主要来分析ViewModel的原理。

ViewModel的生命周期

众所周知,一般使用ViewModel是用来解决两个问题的,第一个就是关于设备配置发生改变时Activity先前状态的保存,在ViewModel出来之前我们一般会使用saveInstanceState这个Bundle来进行状态的保存,但是这样做能存储的数据是有限的,结构也不够明确,ViewModel作为一个生命周期大于Activity的组件就可以帮我们实现状态的存储,下面是ViewModel生命周期与Activity对比的图:
在这里插入图片描述
可以看到直到Activity被完全Destory时ViewModel中的数据才会被清除。

我们使用ViewModel的第二个原因就是用来实现MVVM架构,可以通过DataBinding组件和ViewModel组件以及LiveData组件一起实现MVVM架构,这样可以减轻Activity的职责,避免Activity过于臃肿。

获得ViewModel的提供者

我们从ViewModel的创建开始分析其原理,这里用我们上一篇文章的例子:

mViewModel = ViewModelProvider(this).get(SimpViewModel::class.java)

这里首先会通过ViewModelProvider的构造方法获得一个ViewModelProvider的实例,其构造方法如下所示:

public constructor(owner: ViewModelStoreOwner
) : this(owner.viewModelStore, defaultFactory(owner), defaultCreationExtras(owner))constructor(private val store: ViewModelStore,private val factory: Factory,private val defaultCreationExtras: CreationExtras = CreationExtras.Empty,
)

可以看到我们调用的是第一个构造方法,最终会调用到第二个构造方法中,也就是主构造方法中,这个构造来说就是指定了三个成员变量,分别是ViewModelStore,FactoryCreationExtras三个类型的参数,我们先来分别介绍一下这三个类型。

ViewModelStore – ViewModel的拥有者

着整个类比较小,但是也是有注释的,我们先来看看注释:
在这里插入图片描述
这段注释中比较重要的信息就是ViewModel是真正用来存储ViewModel的类并且在configuration changes就是配置发生改变时新的实例和旧的实例中的信息是一致的。只有当持有者不再会被recreated时里面的数据才会通过clear清除。

接下来我们来看该类的源码:

public class ViewModelStore {private final HashMap<String, ViewModel> mMap = new HashMap<>();final void put(String key, ViewModel viewModel) {ViewModel oldViewModel = mMap.put(key, viewModel);if (oldViewModel != null) {oldViewModel.onCleared();}}final ViewModel get(String key) {return mMap.get(key);}Set<String> keys() {return new HashSet<>(mMap.keySet());}public final void clear() {for (ViewModel vm : mMap.values()) {vm.clear();}mMap.clear();}
}

可以看到具体是用一个哈希表来存储viewModel的实例的,里面唯一比较大的方法就是put方法,里面做的处理就是将替换出来的旧的viewModel实例给清理掉。

Factory – ViewModel的创建工厂

这个Factory是一个定义在ViewModelProvider的内部接口,它的主要职责是用来初始化ViewModel,说实话就是一个工厂。我们来看其定义:

    public interface Factory {public fun <T : ViewModel> create(modelClass: Class<T>): T {throw UnsupportedOperationException("Factory.create(String) is unsupported.  This Factory requires " +"`CreationExtras` to be passed into `create` method.")}public fun <T : ViewModel> create(modelClass: Class<T>, extras: CreationExtras): T =create(modelClass)companion object {@JvmStaticfun from(vararg initializers: ViewModelInitializer<*>): Factory =InitializerViewModelFactory(*initializers)}}

不过这里是一个抽象的,我们来找一个具体的,也就是defaultFactory方法获取的工厂:

public companion object {internal fun defaultFactory(owner: ViewModelStoreOwner): Factory =if (owner is HasDefaultViewModelProviderFactory)owner.defaultViewModelProviderFactory else instance......       
}

这个方法的逻辑就是判断ViewModel的持有者是不是有默认的工厂方法,如果有的话就获取持有者的默认工厂,否则返回的是自身的instance实例,至于这个instance实例是在NewInstanceFactory这个实现了Factory接口的工厂类中定义的伴生变量,具体逻辑是:

@JvmStatic
public val instance: NewInstanceFactory@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)get() {if (sInstance == null) {sInstance = NewInstanceFactory()}return sInstance!!}

可以看到instance是一个静态的单例,他具体指向的还是这个NewInstanceFactory类,至于它是如何初始化/创建ViewModel的实例的我们可以看一眼它的create方法:

override fun <T : ViewModel> create(modelClass: Class<T>): T {return try {modelClass.newInstance()} catch (e: InstantiationException) {throw RuntimeException("Cannot create an instance of $modelClass", e)} catch (e: IllegalAccessException) {throw RuntimeException("Cannot create an instance of $modelClass", e)}
}

这个传入的modelClass就是我们传入的.class文件:

ViewModelProvider(this).get(SimpViewModel::class.java)

所以可以看到,这个默认情况下的工厂就是直接用反射生成了对应ViewModel的实例。

CreationExtras – 构建ViewModel时的额外参数

首先我们来看一看注释的内容:
在这里插入图片描述
简单来说它就是为工厂生成实例的时候提供额外参数的,这些参数使用一个Map来存储的了,不过默认情况下我们并不需要实现额外的工厂,所以这个类型我们先略过。

获得ViewModel的实例

前面我们已经知道了通过构造ViewModelProvider我们可以获得其Factory了,接下来继续向下看:

ViewModelProvider(this).get(SimpViewModel::class.java)

我们看一看get方法做了什么:

    public open operator fun <T : ViewModel> get(modelClass: Class<T>): T {val canonicalName = modelClass.canonicalName?: throw IllegalArgumentException("Local and anonymous classes can not be ViewModels")return get("$DEFAULT_KEY:$canonicalName", modelClass)}public open operator fun <T : ViewModel> get(key: String, modelClass: Class<T>): T {val viewModel = store[key]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] = keyreturn try {factory.create(modelClass, extras)} catch (e: AbstractMethodError) {factory.create(modelClass)}.also { store.put(key, it) }}

可以看到第一个方法会调用第二个方法,其中第一个方法向第二个方法传入的的第一个String类型的参数是通过DEFAULT_KEY和我们传入的类的类名拼接而成的。然后跳转到第二个方法之中去,首先会尝试从ViewModleStroe中获取对应Key对应的ViewModel,但是一般第一次创建时应该会为null,所以之后跳转的应该是最后return块中的factory.create方法之中,这个方法我们在之前Factory的介绍中提到过了,具体就是通过反射实例化ViewModel的,并且最后将其放入到ViewModelStore对象之中去。

至于多次获取同一个ViewModel实例是会跳转到:

 if (modelClass.isInstance(viewModel)) {(factory as? OnRequeryFactory)?.onRequery(viewModel)return viewModel as T} 

这一段中去,这里onRequery默认是无实现的,也就是说并不会对viewModel做任何的处理。

ViewModelStore在哪里被创建

既然ViewModel是被存储在ViewModelStore之中的,那ViewModelStore究竟是在哪里被创建出来的呢?我们可以在ComponentActivity之中找到答案:

public ViewModelStore getViewModelStore() {if (getApplication() == null) {throw new IllegalStateException("Your activity is not yet attached to the "+ "Application instance. You can't request ViewModel before onCreate call.");}ensureViewModelStore();return mViewModelStore;
}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}}
}

可以看到这里在获得ViewModelStore时主要是通过一个NonConfigurationInstances ,而该参数是一个静态的对象,也就是说,它是一个单例,这样就保证了Activity在整个生命周期之中只有一个ViewModelStore实例,从而实现配置改变时也可以恢复数据的作用。

Activity的默认工厂

在看ComponentActivity的源码时,发现了原来Activity也是有默认工厂的,它的具体实现如下:

    constructor(application: Application?, owner: SavedStateRegistryOwner, defaultArgs: Bundle?) {savedStateRegistry = owner.savedStateRegistrylifecycle = owner.lifecyclethis.defaultArgs = defaultArgsthis.application = applicationfactory = if (application != null) getInstance(application)else ViewModelProvider.AndroidViewModelFactory()}

这个方法最终设置到的工厂类都是一个名为AndroidViewModelFactory的工厂类:

    public open class AndroidViewModelFactoryprivate constructor(private val application: Application?,// parameter to avoid clash between constructors with nullable and non-nullable// Application@Suppress("UNUSED_PARAMETER") unused: Int,) : NewInstanceFactory() {@Suppress("SingletonConstructor")public constructor() : this(null, 0)@Suppress("SingletonConstructor")public constructor(application: Application) : this(application, 0)@Suppress("DocumentExceptions")override fun <T : ViewModel> create(modelClass: Class<T>, extras: CreationExtras): T {return if (application != null) {create(modelClass)} else {val application = extras[APPLICATION_KEY]if (application != null) {create(modelClass, application)} else {// For AndroidViewModels, CreationExtras must have an application setif (AndroidViewModel::class.java.isAssignableFrom(modelClass)) {throw IllegalArgumentException("CreationExtras must have an application by `APPLICATION_KEY`")}super.create(modelClass)}}}@Suppress("DocumentExceptions")override fun <T : ViewModel> create(modelClass: Class<T>): T {return if (application == null) {throw UnsupportedOperationException("AndroidViewModelFactory constructed " +"with empty constructor works only with " +"create(modelClass: Class<T>, extras: CreationExtras).")} else {create(modelClass, application)}}@Suppress("DocumentExceptions")private fun <T : ViewModel> create(modelClass: Class<T>, app: Application): T {return if (AndroidViewModel::class.java.isAssignableFrom(modelClass)) {//如果传入的Class类型实现的接口和AndroidViewModel::class一致,及说明也有生命周期,调用构造犯法并且传入application对象try {modelClass.getConstructor(Application::class.java).newInstance(app)} catch (e: NoSuchMethodException) {throw RuntimeException("Cannot create an instance of $modelClass", e)} catch (e: IllegalAccessException) {throw RuntimeException("Cannot create an instance of $modelClass", e)} catch (e: InstantiationException) {throw RuntimeException("Cannot create an instance of $modelClass", e)} catch (e: InvocationTargetException) {throw RuntimeException("Cannot create an instance of $modelClass", e)}} else super.create(modelClass)}public companion object {internal fun defaultFactory(owner: ViewModelStoreOwner): Factory =if (owner is HasDefaultViewModelProviderFactory)owner.defaultViewModelProviderFactory else instanceinternal const val DEFAULT_KEY = "androidx.lifecycle.ViewModelProvider.DefaultKey"private var sInstance: AndroidViewModelFactory? = null@JvmStaticpublic fun getInstance(application: Application): AndroidViewModelFactory {if (sInstance == null) {sInstance = AndroidViewModelFactory(application)}return sInstance!!}private object ApplicationKeyImpl : Key<Application>@JvmFieldval APPLICATION_KEY: Key<Application> = ApplicationKeyImpl}}

具体通过getInstance方法就跳转到了这里,可以发现似乎工厂类都是一个单例的模式,这个工厂的特殊之处就是他的create方法涉及到了Application对象的传入,比如说这里的newInstance方法:

modelClass.getConstructor(Application::class.java).newInstance(app)public T newInstance(Object ... initargs)throws InstantiationException, IllegalAccessException,IllegalArgumentException, InvocationTargetException
{if (serializationClass == null) {return newInstance0(initargs);} else {return (T) newInstanceFromSerialization(serializationCtor, serializationClass);}
}

也就是说整个被传入Application的生命周期内都只有一个实例,这样由于创建的实例在生命周期范围内的单例性和ViewModelStore的单例性,整个ViewModel就可以实现在整个Activity的生命周期内(发生意外,比如说配置改变时)数据不变更的作用。

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

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

相关文章

软件设计师_操作系统基本原理_学习笔记

文章目录 2.1 操作系统概述2.2 进程2.2.1 进程状态转换图2.2.2 前趋图2.2.3 进程的同步与互斥2.2.4 PV操作2.2.5 死锁 2.3 存储管理2.3.1 分区存储管理 2.1 操作系统概述 2.2 进程 2.2.1 进程状态转换图 2.2.2 前趋图 哪些任务可以并行&#xff0c;哪些任务有先后关系&#xf…

VS+Qt+C++ GDAL读取tif图像数据显示

程序示例精选 VSQtC GDAL读取tif图像数据显示 如需安装运行环境或远程调试&#xff0c;见文章底部个人QQ名片&#xff0c;由专业技术人员远程协助&#xff01; 前言 这篇博客针对《VSQtC GDAL读取tif图像数据显示》编写代码&#xff0c;代码整洁&#xff0c;规则&#xff0c;…

Egg使用jwt拦截jtoken验证

安装 npm install egg-jwt注册插件 在config文件夹子下 plugin,js下 use strict;module.exports {//mysqlmysql: {enable: true,package: egg-mysql},//jwtjwt: {enable: true,package: egg-jwt} };使用中间件 在app文件下创建 middleware 文件夹 在middleware 文件下创建…

睿趣科技:新手抖音开店卖什么产品好

抖音已经成为了一款年轻人热爱的社交媒体应用&#xff0c;同时也成为了一种全新的电商平台。对于新手来说&#xff0c;抖音开店卖什么产品是一个备受关注的问题。在这篇文章中&#xff0c;我们将探讨一些适合新手的产品选择&#xff0c;帮助他们在抖音上开店获得成功。 流行时尚…

毛玻璃态卡片悬停效果

效果展示 页面结构组成 页面的组成部分主要是卡片。其中卡片的组成部分主要是包括了图片和详情。 卡片的动效是鼠标悬停在卡片上时&#xff0c;图片会移动到左侧&#xff0c;并且图片是毛玻璃效果。所以我们在布局的时候图片会采用绝对布局。而详情则是基础布局。 CSS3 知识…

【智能家居项目】裸机版本——项目介绍 | 输入子系统(按键) | 单元测试

&#x1f431;作者&#xff1a;一只大喵咪1201 &#x1f431;专栏&#xff1a;《智能家居项目》 &#x1f525;格言&#xff1a;你只管努力&#xff0c;剩下的交给时间&#xff01; 目录 &#x1f3c0;项目简介&#x1f3c0;输入子系统(按键)⚽应用层⚽设备层⚽ 内核层抽象层⚽…

简单三步 用GPT-4和Gamma自动生成PPT PDF

1. 用GPT-4 生产PPT内容 我想把下面的文章做成PPT&#xff0c;请你给出详细的大纲和内容 用于谋生的知识&#xff0c;学生主要工作是学习&#xff0c;成年人的工作是养家糊口&#xff0c;这是基本的要求&#xff0c;在这之上&#xff0c;才能有更高的追求。 不要短期期望过高…

问题: 视频颜色问题,偏绿

参考 什么是杜比视界&#xff1f; - https://www.youtube.com/watch?vldXDQ6VlC7g 【哈士亓说】07&#xff1a;HDR、杜比视界究竟是个啥&#xff1f;为什么这个视频还不是HDR视频&#xff1f; - https://www.youtube.com/watch?vrgb9Xg3cJns 正文 视频应该是 杜比视界 电…

Java 设计模式——抽象工厂模式

目录 1.概念2.结构3.实现4.优缺点5.使用场景6.模式扩展7.JDK 源码解析——Collection.iterator 方法 1.概念 &#xff08;1&#xff09;Java 设计模式——工厂方法模式中考虑的是一类产品的生产&#xff0c;如畜牧场只养动物、电视机厂只生产电视机等。这些工厂只生产同种类产…

653. 两数之和 IV - 输入二叉搜索树

给定一个二叉搜索树 root 和一个目标结果 k&#xff0c;如果二叉搜索树中存在两个元素且它们的和等于给定的目标结果&#xff0c;则返回 true。 输入: root [5,3,6,2,4,null,7], k 9 输出: true 1.第一种思路 中序遍历双指针 即先中序递归遍历并用动态数组ArrayList存储&am…

网络-跨域解决

文章目录 前言一、跨域是什么&#xff1f;二、跨域的解决1.JSONP2.前端代理dev环境3.后端设置请求头CORS4.运维nginx代理 总结 前言 本文主要介绍跨域问题介绍并提供了四种解决办法。 一、跨域是什么&#xff1f; 准确的来说是浏览器存在跨域问题&#xff0c;浏览器为了安全考…

Ubuntu基于Docker快速配置GDAL的Python、C++环境

本文介绍在Linux的Ubuntu操作系统中&#xff0c;基于Docker快速配置Python、C等不同编程语言均可用的地理数据处理库GDAL的方法。 首先&#xff0c;我们访问GDAL库的Docker镜像官方网站&#xff08;https://github.com/OSGeo/gdal/tree/master/docker&#xff09;。其中&#x…

unity 限制 相机移动 区域(无需碰撞检测)

限制功能原著地址&#xff1a;unity限制相机可移动区域&#xff08;box collider&#xff09;_unity限制相机移动区域_manson-liao的博客-CSDN博客 一、创建限制区域 创建一个Cube&#xff0c;Scale大小1&#xff0c;添加组件&#xff1a;BoxCollder&#xff0c;调整BoxColld…

Arcgis克里金插值报错:ERROR 010079: 无法估算半变异函数。 执行(Kriging)失败。

Arcgis克里金插值报错&#xff1a;ERROR 010079: 无法估算半变异函数。 执行(Kriging)失败。 问题描述&#xff1a; 原因&#xff1a; shape文件的问题&#xff0c;此图可以看出&#xff0c;待插值的点有好几个都超出了地理范围之外&#xff0c;这个不知道是坐标系配准的问…

如果在 Mac 上的 Safari 浏览器中无法打开网站

使用网络管理员提供的信息更改代理设置。个人建议DNS解析&#xff0c;设置多个例如114.114.114.114 8.8.8.8 8.8.4.4 如果打不开网站&#xff0c;请尝试这些建议。 在 Mac 上的 Safari 浏览器 App 中&#xff0c;检查页面无法打开时出现的信息。 这可能会建议解决问题的…

2023年中国工业脱水机行业供需分析:随着自动化和智能化技术的快速发展,销量同比增长4.9%[图]

工业脱水机行业是指专门从湿润的固体物料中去除水分的设备制造和相关服务。它广泛应用于食品加工、化工、制药、纺织、环保等行业&#xff0c;用于去除物料中的水分&#xff0c;提高产品质量和降低能耗。 工业脱水机行业分类 资料来源&#xff1a;共研产业咨询&#xff08;共研…

C. MEX Repetition

题目&#xff1a;样例&#xff1a; 输入 5 1 2 1 3 1 0 1 3 2 2 0 2 5 5 1 2 3 4 5 10 100 5 3 0 4 2 1 6 9 10 8输出 1 2 0 1 2 1 2 3 4 5 0 7 5 3 0 4 2 1 6 9 10 思路&#xff1a; 从题目和样例中&#xff0c;我们可以知道&#xff0c;从一个数组中&#xff0c;按照包括0的自…

让大脑自由

前言 作者写这本书的目的是什么&#xff1f; 教会我们如何让大脑更好地为自己工作。 1 大脑的运行机制是怎样的&#xff1f; 大脑的基本运行机制是神经元之间通过突触传递信息&#xff0c;神经元的兴奋和抑制状态决定了神经网络的运行和信息处理&#xff0c;神经网络可以通过…

idea Springboot 教师标识管理系统开发mysql数据库web结构java编程计算机网页源码maven项目

一、源码特点 springboot 教师标识管理系统是一套完善的信息系统&#xff0c;结合springboot框架和bootstrap完成本系统&#xff0c;对理解JSP java编程开发语言有帮助系统采用springboot框架&#xff08;MVC模式开发&#xff09;&#xff0c;系统 具有完整的源代码和数据库&…

蓝桥等考Python组别十级003

第一部分&#xff1a;选择题 1、Python L10 &#xff08;15分&#xff09; 已知s Pencil&#xff0c;下列说法正确的是&#xff08; &#xff09;。 s[0]对应的字符是Ps[1]对应的字符是ns[-1]对应的字符是is[3]对应的字符是e 正确答案&#xff1a;A 2、Python L10 &am…