【Android】ViewModel原理分析

2023-11-04

概述

本文主要通过分析ViewModel源码解决以下两个疑问:
1、ViewModel如何保证的唯一性
2、ViewModel如何保证数据不丢失的


为了解决这些问题,从ViewModel的构造开始,一般创建ViewModel的方法如下:

ViewModelProvider(this, defaultViewModelProviderFactory).get(MyViewModel::class.java)

很明显,ViewModel的创建使用了工厂模式,这里使用默认工厂,接下来看一下get方法:

public <T extends ViewModel> T get(@NonNull Class<T> modelClass) {    String canonicalName = modelClass.getCanonicalName();    
	if (canonicalName == null) {        
	throw new IllegalArgumentException("Local and anonymous classes can not be ViewModels");    
	}    
	return get(DEFAULT_KEY + ":" + canonicalName, modelClass);  // 1
}

在注释1继续追踪,可以看到如下代码:

public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
    ViewModel viewModel = mViewModelStore.get(key);

    if (modelClass.isInstance(viewModel)) {
        if (mFactory instanceof OnRequeryFactory) {
            ((OnRequeryFactory) mFactory).onRequery(viewModel);
        }
        return (T) viewModel;
    } else {
        //noinspection StatementWithEmptyBody
        if (viewModel != null) {
            // TODO: log a warning.
        }
    }
    if (mFactory instanceof KeyedFactory) {
        viewModel = ((KeyedFactory) mFactory).create(key, modelClass);
    } else {
        viewModel = mFactory.create(modelClass);
    }
    mViewModelStore.put(key, viewModel);
    return (T) viewModel;
}

这里通过key(就是之前传过去自己实现的ViewModel的类的名字)在mViewModelStore中拿出了viewModel。重点来了,mViewModelStore涉及到了之前提到的一个问题,ViewModel如何保证唯一性的。mViewModelStore是一个ViewModelStore对象,接下来看一下这个对象:

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());
    }

    /**
     *  Clears internal storage and notifies ViewModels that they are no longer used.
     */
    public final void clear() {
        for (ViewModel vm : mMap.values()) {
            vm.clear();
        }
        mMap.clear();
    }
}

可以看出底层是通过一个Map来存储ViewModel实例的,同时这个Map也保证了一个Activity中只能有一个对应类型的ViewModel存在,为了进一步说明,可以看到ViewModelProvider的构造方法

public ViewModelProvider(@NonNull ViewModelStoreOwner owner, @NonNull Factory factory) {
    this(owner.getViewModelStore(), factory);
}

这里的ViewModelStoreOwner是一个接口,很多Activity以及Fragment都实现了这个接口,这里以AppCompatActivity为例:
请添加图片描述
AppCompatActivity继承自FragmentActivity,FragmentActivity继承自ComponentActivity,在ComponentActivity实现了ViewModelStoreOwner接口,这里会通过ensureViewModelStore创建ViewModelStore。

void ensureViewModelStore() {
    if (mViewModelStore == null) {
        NonConfigurationInstances nc =
                (NonConfigurationInstances) getLastNonConfigurationInstance();	//1
        if (nc != null) {
            // Restore the ViewModelStore from NonConfigurationInstances
            mViewModelStore = nc.viewModelStore;
        }
        if (mViewModelStore == null) {
            mViewModelStore = new ViewModelStore();
        }
    }
}

注释1处会拿到onRetainNonConfigurationInstance方法的返回值

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
        NonConfigurationInstances nc =
                (NonConfigurationInstances) getLastNonConfigurationInstance();
        if (nc != null) {
            viewModelStore = nc.viewModelStore;
        }
    }

    if (viewModelStore == null && custom == null) {
        return null;
    }

    NonConfigurationInstances nci = new NonConfigurationInstances();
    nci.custom = custom;
    nci.viewModelStore = viewModelStore;
    return nci;
}

可以看出这里保存了一个NonConfigurationInstances实例,这个实例中保存之前的viewModelStore实例,这就是ViewModel不会随着Activity销毁而销毁的原因,具体是在什么时机调用的可以继续追踪,最后,可以得到如下调用链:

onRetainNonConfigurationInstance -> LocalActivityManager.dispatchRetainNonConfigurationInstance -> ActivityGroup.onRetainNonConfigurationChildInstances -> Activity.retainNonConfigurationInstances -> ActivityThread.performDestroyActivity

也就是说在Activity执行destroy方法的时候会保存一下当时的信息,ViewModel也在那个时候被保存下来了。


总结

现在回到上面那两个问题:

1、如何保证的唯一性
ViewModel是根据ViewModel实现类的名字保存在一个HashMap中的,HashMap保证了ViewModel的唯一性

2、如何保证数据不丢失的
在Activity执行destory方法的时候,会保存当前的ViewModelStore,等到下次重走生命周期的时候会重新拿到上次的ViewModel,也就保证了数据不会发生丢失。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【Android】ViewModel原理分析 的相关文章

  • LocalDate 减去 period 得到错误的结果

    LocalDate减去一个Period 如 28年1个月27天 得到错误的结果 但减去一个Period 只有天单位 如 10282 天 得到正确的结果 有什么需要注意的吗 public static void main String arg
  • Android 媒体播放器搜索栏

    我有一个创建 播放和处理媒体播放器 只是音频 的服务 但我在主要活动中有一个搜索栏 我想自然地显示音频文件的进度并允许用户搜索到不同的位置 我花了很长时间才弄清楚 将 UI 中的搜索栏连接到服务中的媒体播放器的最佳或正确方法是什么 我将这样
  • 如何使用 swagger-codegen-plugin (maven) 生成客户端代码?

    我需要使用 swagger codegen plugin for maven 在 eclipse 中生成服务器存根代码 你能帮忙怎么做吗 以及需要什么配置 在 pom xml 中 我找到了这个答案 您只需要像下面这样更改 pom xml 即
  • MongoDB java 驱动程序 3.0 在身份验证时无法捕获异常

    我超级卡住o 0 在尝试通过 Java 驱动程序进行身份验证时 存在捕获异常的问题 正如你可能会看到的Throwable类不工作 private MongoClient mongoClient private MongoDatabase m
  • LinearLayout:防止最后一个孩子被之前的大文本视图推出或挤压

    我有一个LinearLayout里面有两个孩子 第一个是TextView对于动态内容 第二个是一个按钮 我的问题是按钮被推出其父级或被挤压到不再可见的程度 我想要TextView认识到其父母与第二个孩子一起没有更多空间 并开始新的一行 而不
  • 分离 Fragment 和删除 Fragment 有什么区别?

    在 Android 文档中碎片交易 http developer android com reference android app FragmentTransaction html我注意到两种非常相似的方法 detach and remo
  • java swing:向 JTree 项目添加自定义图形按钮

    我想在 JTree 中的项目右侧添加一个带有小图标的附加按钮 这可以做到吗 如果是这样 怎么办 thanks Clamp 你在这方面成功了吗 我想做同样的事情 但很难让 JButton 响应用户 设置渲染器以显示按钮的过程很顺利 但所有鼠标
  • 通知操作而不启动新活动?

    我计划提供一个包含两个操作的提醒通知 一个用于批准登录请求 一个用于拒绝登录请求 通过单击这些操作中的任何一个 我希望向我的服务器发出 HTTP 请求 最重要的是 我不想启动新的 Activity 或根本不想将用户重定向到我的应用程序 Co
  • 如何向开关对象添加/更改波纹效果

    下面是我自定义的开关 红圈是默认的波纹效果 我发现设置一个波纹可绘制作为开关的背景 控制波纹的颜色
  • 从浏览器访问本地文件?

    您好 我想从浏览器访问系统的本地文件 由于涉及大量安全检查 是否可以通过某种方式实现这一目标 或使用 ActiveX 或 Java Applet 的任何其他工作环境 请帮帮我 要通过浏览器访问本地文件 您可以使用签名的 Java Apple
  • 如何检测日期选择器对话框的取消单击?

    我正在使用以下 日期选择器的示例 http developer android com guide tutorials views hello datepicker html http developer android com guide
  • 如何将库添加到 LIBGDX 项目的依赖项 gradle

    一切都在问题中 我已经尝试了在 SO 和其他网站中找到的所有答案 但没有运气 这就是我迄今为止尝试过的 adding compile fileTree dir lib include jar 到我的 build gradle adding
  • 在 Freemarker 模板中检查 Spring 安全角色和记录的用户名

    有谁知道 freemarker 标签来检查 freemarker 文件中的 spring 安全角色和用户名 我从网络上的几个资源中发现以下代码将打印登录的用户名 但它没有打印用户名 而是打印 登录为
  • 如何解决android程序中的警告“从不本地读取”

    为什么我收到警告说 The field testscreen ScaleAnimToShow mVanishAfter is never read locally testscreen java testscreen src com tes
  • Java 中的微分方程

    我正在尝试用java创建一个简单的SIR流行病模型模拟程序 基本上 SIR 由三个微分方程组定义 S t l t S t I t l t S t g t I t R t g t I t S 易感人群 I 感染人群 R 康复人群 l t c
  • Android Volley - 发布请求 - 无法在线工作

    我试图通过 Volley 发出 POST 请求 它在我的本地主机中工作得很好 但是当我将它移动到网络服务器时 响应为空 Java代码 RequestQueue queue Volley newRequestQueue this String
  • 如何减少 Android 中浮动 editText 提示和 editText 框之间的空间?

    我有一个带有浮动提示的 EditText 但我想知道如何减少浮动提示和 EditText 框之间的空间 现在我的用户界面看起来像https i stack imgur com ltfra jpg https i stack imgur co
  • 如何使用注释处理 Hibernate 和 Spring 中的连接查询?

    我正在使用 Spring 和 Hibernate 以及 MySQL 开发应用程序 我是 Hibernate 新手 完成了基本任务 现在我需要在选择查询中应用联接以使用注释从多个表中获取数据 我已经搜索过但仍然没有任何想法 这是我的数据库表和
  • Android Google 地图无法在当前主题中找到样式“mapViewStyle”

    添加谷歌地图视图时 我扩展了MapView 使用xml编辑器将其添加到活动中 并将我的谷歌地图api密钥手动添加到布局xml文件中 我的权限在清单文件中允许互联网 我想知道的是 在 xml 编辑器中 我收到错误 无法在当前主题中找到样式 m
  • 使用 AmazonSNSClient 发送短信时的授权

    aws 官方文档如何发送短信 http docs aws amazon com sns latest dg sms publish to phone html使用 java 中的 aws SDK 非常简单 但是 当发送如底部示例所示的消息时

随机推荐

  • 浏览器无痕模式有什么作用,手机浏览器开启无痕模式的方法

    在我们的手机基本上都安装了浏览器 当我们在上网过程中 不想浏览记录被留下 那么开启无痕模式是非常有必要的 那么 浏览器的无痕模式有什么作用 手机浏览器如何开启无痕模式呢 下面教大家如何在手机浏览器中开启无痕模式 赶紧学一学吧 关键时刻能用上
  • 机械臂正运动学标准DH参数建立技巧

    1 切记 i 坐标系建立在i 1关节轴上 如 0 坐标系建立在关节1轴上 依次类推 6 坐标系与 5 坐标系姿态一致 固连在法兰盘接口末端 2 坐标系原点建立 若1 2轴垂直或异面垂直 则坐标系 1 原点在1轴与2轴的交点 0 坐标系原点建
  • 2.2.1 数据通信系统的模型

    一个数据通信系统分为三大部分 1 源系统 或发送端 发送方 2 传输系统 或传输网络 3 目的系统 或接收端 接收方 数据通信系统模型如下 上图中调制解调器有2个功能 1 调制 将计算机发出的低频 数字信号 转换成传输媒介可以传输的 模拟信
  • fetch中断请求, 和再次恢复使用

    业务场景 当时用fetch 建立长连接请求 在不使用时需要将其断掉 以缓解带宽压力和浏览器运行压力 等再次需要建立长链接时 再次启用 1 外层定义controller 一旦中止 AbortController就会被消耗 每次调用都必须创建新
  • Python基础学习-简要记录

    目录 快捷键 基础 1 字符串 2 变量 3 序列 4 列表 5 元组 6 字典 7 集合 8 time 模块 9 datetime 模块 date time datetime 类 10 calendar 模块 Calendar 类 Tex
  • 在1行上输入5个数字,数字之间用英文半角逗号分隔。输出其中最小的数字。 结果保留2位小数。

    题目描述 在1行上输入5个数字 数字之间用英文半角逗号分隔 输出其中最小的数字 结果保留2位小数 输入 6 4 5 2 3 输出 2 00 样例输入 Copy 12 22 2 32 42 样例输出 Copy 2 00 a map eval
  • undo表空间故障恢复

    time 2008 04 15author skate 参考文档 http blog chinaunix net u 7667 showart 163271 html undo表空间故障恢复 ORA 00376 file 2 cannot
  • mysql查询排名前5的语句_MySQL语句实现排名

    首先我们创建一张city popularity表 CREATE TABLEcity popularity regionint 10 NOT NULL COMMENT 1 国内 2 海外 city nameVARCHAR 64 NOT NUL
  • Vue.js全家桶仿哔哩哔哩动画 (移动端APP)

    项目地址 由于项目是移动端 电脑访问时可以切换成手机端 播放页面其实没有根据B站移动端来 比较粗糙 源码地址 欢迎Star 在线预览 项目描述 前端部分 实现的Swiper Toast Indicator组件 来自Mint ui 使用了Vu
  • 【HDFS】EditLogTailer功能及原理(二)-- selectInputStreams细节详解

    HDFS EditLogTailer功能及原理 一 整体流程 HDFS EditLogTailer功能及原理 二 selectInputStreams细节详解 HDFS EditLogTailer功能及原理 三 loadEdits方法细节详
  • Javascript变量提升预解析的理解

    预解析 JavaScript代码的执行是由浏览器中的JavaScript解析器来执行的 JavaScript解析器执行JavaScript代码的时候 分为两个过程 预解析过程和代码执行过程 预解析过程 把变量的声明提升到当前作用域的最前面
  • 使用python的pandas库把.data文件转化为csv文件

    1 问题引入 在数据分析 机器学习 深度学习中 我们经常会处理各种各样格式的数据 今天 博主在做房价预测时 采用波士顿房价数据集 从网上下载的数据集格式为 data 并不是我们喜闻乐见的csv格式 所以想采用pandas库将其转为为csv格
  • 【Redis】Redis 的学习教程(十)之使用 Redis 实现消息队列

    消息队列需要满足的要求 顺序一致 要保证消息发送的顺序和消费的顺序是一致的 不一致的话可能会导致业务上的错误 消息确认机制 对于一个已经被消费的消息 已经收到ACK 不能再次被消费 消息持久化 要具有持久化的能力 避免消息丢失 这样当消费者
  • linux怎么将磁盘剩余空间给分区,利用fdisk将硬盘剩余空间进行分区

    1 首先查看分区 发现300多G的硬盘 dev sdc1只使用了200多G而已 root local103 dbbackup df h Filesystem Size Used Avail Use Mounted on dev sda2 1
  • [黑科技] 使用Word和Excel自制题库自判断答题系统

    这篇文章是LZY老师告诉我的一个方法 如果你需要做题库 并且喜欢电子答题的方法 这篇文章或许会对你有所帮助 反正李老师班级的平均成绩高出其他班级的14分 这就是它的好处 希望这篇文章对我今后的学生有所帮助吧 注意 这篇文章涉及到Word特殊
  • 详解分布式共识(一致性)算法Raft

    分布式共识及Raft简介 所谓分布式共识 consensus 与CAP理论中的一致性 consistency 其实是异曲同工 就是在分布式系统中 所有节点对同一份数据的认知能够达成一致 保证集群共识的算法就叫共识算法 它与一致性协议这个词也
  • PyTorch 更改训练好的模型 继续训练

    目录 直接加载预训练模型 加载部分预训练模型 冻结部分参数 训练另一部分参数 微改基础模型预训练 微改基础模型 简单预训练 直接加载预训练模型 如果我们使用的模型和原模型完全一样 那么我们可以直接加载别人训练好的模型 my resnet M
  • SHADER学习笔记(一):Surface Shader

    Surface Shader是Unity为了方便shader编写提供的特殊功能 它对底层的vertex fragment shader做了封装 省去了一些重复代码编写的工作量 我的理解是它同时具有vertex fragment shader
  • [CISCN 2022 初赛]login_normal

    叠甲 菜 很菜 就是懂一点但是不多 可能涉及的错误会很多 有错误欢迎指出 同时对于这个疑问有解答的也欢迎留言 总之就是很菜 QAQ 这一道题 首先要考代码审计 来猜它这个 login 的格式 然后在通过它的 login 之后 通过传入可见字
  • 【Android】ViewModel原理分析

    概述 本文主要通过分析ViewModel源码解决以下两个疑问 1 ViewModel如何保证的唯一性 2 ViewModel如何保证数据不丢失的 为了解决这些问题 从ViewModel的构造开始 一般创建ViewModel的方法如下 Vie