Jetpack 之 ViewModel

news/2024/4/29 8:45:19/文章来源:https://blog.csdn.net/zgcqflqinhao/article/details/127257509

Jetpack 系列第三篇,这次回顾 ViewModel,ViewModel 作为 MVVM 架构中的 VM 层,具有自己的生命周期,且生命周期贯穿整个 Activity 或 Fragment,相较于之前 MVP 的 Presenter,它的存活时间更长,所以作为界面数据的持有者和控制者会更有优势 。

一、ViewModel 有什么用

  1. 首先,ViewModel 可以将视图层的逻辑剥离出来,所有与视图相关的数据应该存在于 ViewModel 中,而不是 Activity 或 Fragment,这一点它与 Presenter 作用类似,皆致力于减少 View 层逻辑;
  2. 相较于之前 MVP 的 Presenter,它的生命周期更长,在 Activity 因配置改变而重建时,ViewModel 的数据仍可得到保存。
  3. 可指定作用域,实现 Activity 与 Fragment 之间或 Fragment  与 Fragment  之间数据共享。

二、ViewModel 怎么用

ViewModel 作为一个抽象类,当我们自己定义一个类继承自它的时候,由于它也是一个普通的类,所以也可以用 new 的方式创建实例,但是这样做的话就失去它的意义了,在 Activity 重建时,ViewModel 也会重新创建。

package com.qinshou.jetpackdemo.viewmodelimport androidx.lifecycle.ViewModelclass TestViewModel : ViewModel() {var i = 0
}
package com.qinshou.jetpackdemo.viewmodelimport android.os.Bundle
import android.util.Log
import android.widget.Button
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import com.qinshou.jetpackdemo.Rclass ViewModelActivity : AppCompatActivity() {companion object {const val TAG = "ViewModelActivity"}override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_viewmodel)val testViewModel = TestViewModel()Log.i(TAG, "testViewModel--->$testViewModel")findViewById<Button>(R.id.btn_set_value).setOnClickListener {testViewModel.i++}findViewById<Button>(R.id.btn_get_value).setOnClickListener {Toast.makeText(this, "Value of view model is ${testViewModel.i}", Toast.LENGTH_SHORT).show()}}
}

直接 new 创建 ViewModel 实例,然后横竖屏切换一下,可以看到 ViewModel 不是同一个实例,而且 ViewModel 中持有的数据也被重新初始化了:

正确的做法应该是使用 ViewModelProvider 去创建 ViewModel,我们将创建 ViewModel 实例的方式修改一下,然后重新运行:

val testViewModel = ViewModelProvider(this).get(TestViewModel::class.java)

 

 可以看到横竖屏切换后,ViewModel 内存地址不变,而且持有的数据也得以保存。那么为什么通过 ViewModelProvider 就可以保证这样的效果呢,小追一下源码。

首先 ViewModelProvider 有三个构造方法:

    public ViewModelProvider(@NonNull ViewModelStoreOwner owner) {this(owner.getViewModelStore(), owner instanceof HasDefaultViewModelProviderFactory? ((HasDefaultViewModelProviderFactory) owner).getDefaultViewModelProviderFactory(): NewInstanceFactory.getInstance());}public ViewModelProvider(@NonNull ViewModelStoreOwner owner, @NonNull Factory factory) {this(owner.getViewModelStore(), factory);}public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) {mFactory = factory;mViewModelStore = store;}

这三个构造方法最后走到了同一个地方,通常情况下我们使用只穿一个 ViewModelStoreOwner 参数的构造方法即可,最后它会保存 Factory 和 ViewModelStore 实例。在默认实现中,这两个实例由 ComponentActivity 或者 Fragment 去创建(这里仅贴了 ComponentActivity 中的代码):

    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();}}}
    public ViewModelProvider.Factory getDefaultViewModelProviderFactory() {if (getApplication() == null) {throw new IllegalStateException("Your activity is not yet attached to the "+ "Application instance. You can't request ViewModel before onCreate call.");}if (mDefaultFactory == null) {mDefaultFactory = new SavedStateViewModelFactory(getApplication(),this,getIntent() != null ? getIntent().getExtras() : null);}return mDefaultFactory;}

然后我们是通过 ViewModelProvider 的 get() 方法获取 ViewModel:

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

该方法将 ViewModel 的类名加上 DEFAULT_KEY 前缀作为 key 再次调用了 get 方法重载:

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

这个方法主要做的事就是先从 ViewModelStore 中获取 ViewModel 实例,有就直接返回,没有的话就通过 Factory 创建然后放到 ViewModelStore 中再返回,相当于一个缓存机制,而获取和存储 ViewModel 的是在 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();}
}

可以看到 ViewModelStore 中其实是用一个 HashMap 去存储 ViewModel 实例的,那它到底是怎么保证 Activity 在异常销毁时保证 ViewModel 不变的呢?是不是只要在 Activity 异常销毁时,保存 ViewModelStore 的实例,重新创建 Activity 时再恢复就好了呢?事实上就是这样:

    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 NonConfigurationInstanceNonConfigurationInstances 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;}

在 Activity 异常销毁时会通过调用 onRetainNonConfigurationInstance(),将 ViewModelStore 保存在 NonConfigurationInstances 对象中,而刚才创建 ViewModelStore 的代码中,首先会从最后保存 NonConfigurationInstances 对象中获取,如果之前有保存,就直接拿来使用,所以系统内部就已经帮我们做好了异常销毁时的状态保存了。

三、ViewModel 作用域

关于这一点,其实我们通过刚才的源码知道 ViewModel 是如何创建出来的,那它作用域的问题就一目了然了。因为 ViewModel 其实是通过 ViewModelStore 对象获取的,而 ViewModelStore 是在创建 ViewModelProvider 对象时赋的值。那么在 Fragment 中,如果我们传递的 ViewModelStoreOwner 是 this,那就是它自己的 ViewModelStore,如果传递是 requireActivity(),那使用的就是宿主 Activity 的 ViewModelStore,这样就能达到多个子 Fragment 中共享数据的目的了。小做演示:

class ViewModelFragment1 : Fragment() {companion object {const val TAG = "ViewModelFragment1"}override fun onCreateView(inflater: LayoutInflater,container: ViewGroup?,savedInstanceState: Bundle?): View? {return LayoutInflater.from(context).inflate(R.layout.fragment_viewmodel, null, false)}override fun onViewCreated(view: View, savedInstanceState: Bundle?) {super.onViewCreated(view, savedInstanceState)val testViewModel = ViewModelProvider(this).get(TestViewModel::class.java)Log.i(TAG, "testViewModel--->$testViewModel")view.findViewById<TextView>(R.id.text_view).text = "Value of view model is ${testViewModel.i}"}
}
class ViewModelFragment2 : Fragment() {companion object {const val TAG = "ViewModelFragment1"}override fun onCreateView(inflater: LayoutInflater,container: ViewGroup?,savedInstanceState: Bundle?): View? {return LayoutInflater.from(context).inflate(R.layout.fragment_viewmodel, null, false)}override fun onViewCreated(view: View, savedInstanceState: Bundle?) {super.onViewCreated(view, savedInstanceState)val testViewModel = ViewModelProvider(requireActivity()).get(TestViewModel::class.java)Log.i(TAG, "testViewModel--->$testViewModel")view.findViewById<TextView>(R.id.text_view).text = "Value of view model is ${testViewModel.i}"}
}

这两个 Fragment 代码除了创建 ViewModelProvider 传递的参数不一样,其他都一样,用了一个文本框去显示 ViewModel 中的数据,然后在刚才的 Activity 中增加两个按钮分别显示这两个 Fragment(代码太简单就不贴了),效果如下:

可以看到 ViewModelFragment1 中的 ViewModel 对象内存地址与宿主 Activity 不一样,里面的数据值也不一样,而 ViewModelFragment2 是通过宿主 Activity 去获取的 ViewModelProvider 从而获取的 ViewModel 对象,所以内存地址和数据值都跟宿主 Activity 是一样的。

四、总结

ViewModel 作为 MVVM 中的重要一环,相当于 Presenter 的升级版,结合 LiveData 使用,可以节省很多之前需要注意的操作。ViewModel 的使用也不难,在了解它的特性是如何去实现后,相信运用起来会更加得心应手。

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

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

相关文章

商用图片素材,高清无水印

今天给大家分享8个免费、商用图片素材网站&#xff0c;全部高清无水印&#xff0c;轻松应对各种场景。1、菜鸟图库 https://www.sucai999.com/pic.html?vNTYwNDUx菜鸟图库是一个综合性素材网站&#xff0c;这里面有很多设计、图片、视频、音频等素材&#xff0c;图片素材全部都…

vscode 提示 vetur can‘t find `tsconfig.json`的解决办法

VSCode&#xff08;全称&#xff1a;Visual Studio Code&#xff09;是一款由微软开发且跨平台的免费源代码编辑器。该软件支持语法高亮、代码自动补全&#xff08;又称 IntelliSense&#xff09;、代码重构、查看定义功能&#xff0c;并且内置了命令行工具和 Git 版本控制系统…

SEO作弊有哪些手段,网站采用SEO作弊会带来哪些惩罚

在做网站SEO优化过程中&#xff0c;有的人为了快速提高网站排名&#xff0c;采用了各种各样的方法。有的甚至采用SEO作弊的手段来优化网站&#xff0c;短期内提升了网站的排名。但是&#xff0c;我们要知道&#xff0c;做SEO优化欲速则不达&#xff0c;SEO作弊会给网站带来一定…

部署CentOS可视化界面GUI-之腾讯云服务器

目录 一、购买云服务器实例 二、配置安全组、设置管理员密码 三、远程登录 四、安装CentOS可视化界面GUI 4.1、系统GUI配置 4.2、系统GUI配置 一、购买云服务器实例 二、配置安全组、设置管理员密码 三、远程登录 用其控制台下webShell&#xff0c;或VNC模式&#xff0…

《MySQL实战45讲》——学习笔记08 “一致性视图、可重复读实现“

这篇文章讲的比较分散&#xff0c;这里做一个梳理&#xff0c;先将简单的概念如"事务的启动时机"、"视图"、"秒级创建快照"拎出来解释&#xff0c;然后通过文章中的几个例子说明"一致性读"和"当前读"&#xff1b; 08 | …

AspectJ in action

Discovering AOP This chapter covers ■ Understanding crosscutting concerns ■ Modularizing crosscutting concerns using AOP ■ Understanding AOP languages Reflect back on your last project, and compare it with a project you worked on a few years back. Wha…

一文带你快速鉴别CookieSession

文章目录会话跟踪技术1、相关基础概念2、Cookie2.1 Cookie的基本使用2.1.1 发送Cookie2.1.2 获取Cookie2.2 Cookie原理2.3 Cookie存活时间2.4 Cookie存储中文3、Session3.1 Session的基本使用3.2 Session原理3.3 Session的钝化和活化3.4 Session的存活时间总结会话跟踪技术 1、…

SSl证书协议作用

SSl证书协议作用 随着移动互联网时代的飞速发展&#xff0c;似乎每周都能看到很多关于数据泄露的新闻&#xff0c;而且报道还在不断涌现。在统计的100款app中&#xff0c;有多达91款app收集了过多的用户个人信息。 为了改善这一现象&#xff0c;网络空间管理局联合发布了《认定…

TRC丨艾美捷TRC 2-氨基-2-甲基丙酰胺说明书

艾美捷TRC 2-氨基-2-甲基丙酰胺化学性质&#xff1a; 目录号A010210 化学名称2-氨基-2-甲基丙酰胺 CAS 编号16252-90-7 分子式C₄H₁₀N2O 外貌白色固体 熔点>250C&#xff08;分解&#xff09; 分子量102.14 溶解度甲醇&#xff08;少量&#xff09; 类别建筑模块…

听说2022金九银十变成铜九铁十了......

往年的金九银十&#xff0c;今年被戏称为“铜九铁十”。知名的大厂HR们都在不断的裁员&#xff0c;能被保住不被裁掉可能就万事大吉了&#xff0c;赛道越来越窄&#xff0c;都在预测未来计算机行业是不是下一个土木工程&#xff1f; 我也算是软件测试岗位的老鸟了&#xff0c;…

计算机网络03之可靠传输

1. 停止等待协议 1.概述 发送方每次只能发送一个数据包&#xff0c;确认方每次只能发送一个确认。发送方收到重复的确认会丢弃&#xff08;接收方已经接收&#xff09;&#xff0c;接收方收到重复的数据&#xff0c;会把数据丢弃&#xff0c;但是会发送确认&#xff08;防止上…

DETR:End-to-End Object Detection with Transformers

论文地址&#xff1a;https://arxiv.org/abs/2005.12872 代码地址&#xff1a;https://github.com/facebookresearch/detr 在看完Transformer之后&#xff0c;将会开始看视觉类的Transformer应用。本篇论文出自ECCV20&#xff0c;是关于目标检测的论文。DETR&#xff0c;即Det…

pyinstaller打包多个python程序

以下两个python文件 get_file_message_main.py为执行文件&#xff0c;继承了get_file_message.py中的类 打开终端cmd 切换到桌面 cd desktop切换到指定路径 cd python打包pyi-makespec pyi-makespec get_file_message_main.py生成get_file_message_main.spec文件 .spec文…

信息增益计算和决策树生长过程

信息增益计算和决策树生长过程 给定训练集S&#xff0c;下面以信息增益作为最佳划分的标准&#xff0c;演示信息增益的计算和决策树生长的过程&#xff1a; 根节点 &#xff08;1&#xff09;以“Outlook”被选做划分属性 总共有14条数据&#xff0c;打球9条&#xff0c;不打…

[Windows内核源码分析5] 引导过程(对象管理器初始化在Phase1部分的分析)

在第1阶段, ObInitSystem首先对每个处理器的PRCB结构的lookaside链表进行初始化。 在全局名字空间中创建根目录\ 来看一下NtCreateDirectoryObject这个函数实际上是ObCreateObject和ObInsertDirectory的封装。其内部执行的操作很简单&#xff0c;一个是创建一个目录对象, 接着…

y160.第九章 GitOps从入门到精通 -- Tekton Trigger(九)

8.Tekton Trigger 8.1 Tekton Trigger 基础 Tekton Triggers简介 监控特定的事件,并在满足条件时自动触发Tekton Pipeline; 例如,代码仓库上的创建pull request、push代码,以及合并pull request至main分支等Tekton Triggers为用户提供了一种声明式API 它允许用户按需定义监…

客户管理系统(SSM版):解除线索关联市场活动

一、客户需求&#xff1a; 用户在线索明细页面,点击某一个"解除关联"按钮,弹出确认解除的窗口; 用户点击"确定"按钮,完成解除线索关联市场活动的功能. *解除成功之后,刷新已经关联的市场活动列表 *解除失败,提示信息,列表也不刷新 二、功能实现 1.首…

openjdk源码准备编译和依赖

在Windows系统上进行openjdk的源码编译 一、准备编译需要的装备 1.首先下载一个软件Cygwin。这个软件是一个在Windows平台下模拟Linux运行环境的软件&#xff0c;提供了一系列的Linux的运行命令。&#xff08;解释这些&#xff0c;有兴趣的自己百度&#xff09; 下载的路径点…

Web APIs:事件基础

事件三要素 1.事件是有三部分组成 事件源 事件类型 事件处理程序 &#xff08;1&#xff09;事件源 事件被触发的对象 谁 按钮 &#xff08;2&#xff09;事件类型 如何触发 什么事件 比如鼠标点击&#xff08;click&#xff09;&#xff0c;经过 还是键盘按下 &…

TRC丨艾美捷TRC D-Abequose说明书

艾美捷TRC D-Abequose是一种甜味剂和增味剂配方&#xff0c;适用于食品、饮料、药物和化妆品用途。 艾美捷TRC D-Abequose化学性质&#xff1a; 目录号A010205 化学名称D-Abequose CAS 编号56816-60-5 分子式C₆H₁₂O₄ 分子量148.16 贮存4C 溶解度甲醇&#xff08;少许…