一文带你了解ViewModel

2023-11-11

Lifecycle库可以有效避免内存泄漏和解决常见的Android生命周期难题。

1.引言

ViewModel属于lifecycle(生命周期感知型组件)中的一员,通常与LiveData、DataBinding一起使用,它们是MVVM架构的重要成员。ViewModel类旨在以注重生命周期的方式存储和管理界面相关的数据。

2.ViewModel是什么

ViewModel是一个用来管理UI数据的组件。

管理UI数据?UI数据不是写在Activity和Fragment中吗?是的。

啊不是。。。自ViewModel发布,Google官方建议将应用所有的UI数据保存在ViewModel中,而不是Activity中。那么使用ViewModel来管理UI数据有哪些好处呢?

我们知道,当应用退回到桌面,或者旋转屏幕时,Activity很可能会被销毁重建,页面的状态将会丢失。为了数据的保存和恢复,常规的做法是通过onSavaInstanceState()和onRestoreInstanceState()实现,但这种方式仅适合保存少量可以被序列化、反序列化的数据,不能保存比较大的数据或不方便序列化,反序列化的数据。

那么,有没有一个类,在Activity发生这些变更时,依旧保留这个类的实例,当Activity重建后拿到的还是这个实例,那么只要把UI数据统一放到这个类中,就能保持页面的状态不会丢失了。

有的,这个类就是ViewModel。下图说明了ViewModel在Activity经历屏幕旋转而后结束的过程中所处的各种生命周期状态。

在这里插入图片描述

这些基本状态同样适用于Fragment的生命周期。从图中可以看到,ViewModel是在Activity真正结束时才被清理掉。

3.ViewModel的使用

添加依赖

  dependencies {
    def lifecycle_version = "2.2.0"
    implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
  }

创建ViewModel很简单,仅需要继承ViewModel类,像这样

class MainViewModel  : ViewModel() {

    var count = 0

}

通过ViewModelProvider框架拿到ViewModel实例,而不是new一个ViewModel实例,这种机制可以让你的ViewModel独立于配置更改:

class MainActivity : AppCompatActivity() {

    private lateinit var  mViewModel : MainViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        mViewModel = ViewModelProvider(this).get(MainViewModel::class.java)
        tv.text = mViewModel.count.toString()
        btn.setOnClickListener {
            mViewModel.count++
            tv.text = mViewModel.count.toString()
        }
    }

}

ViewModelProvider的构造函数需要传入ViewModelStoreOwner的实例,不用担心,AppCompatActivity和Fragment已经间接或直接的实现了它。现在,count的值不会因为屏幕旋转而清零了:

在这里插入图片描述

现在我们知道,ViewModel可以实例贯穿着Activity的整个生命周期,那么是否可以借助ViewModel来实现Fragment之间的数据共享与通信呢?

答案是肯定的,这样做的好处是,Activity不需要执行任何操作,也不需要对此通信有任何了解。在ViewModel之前,常规的做法都是Actiivty和Fragment之间定义相应的接口或者使用EventBus等组件。说实话,这两种方式都不太友好。

还是刚才的例子,我们来改造一下:

class MainViewModel  : ViewModel() {

    var count =  MutableLiveData<Int>(0)
}

如果对LiveData不太熟悉,这里就先把它当成Observable吧。

然后我们在MainActivity中定义上下两个Fragment:

class TopFragment : Fragment() {

    private lateinit var  mViewModel : MainViewModel

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        return inflater.inflate(R.layout.fr_top,container,false)
    }
    override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)
        mViewModel = ViewModelProvider(requireActivity()).get(MainViewModel::class.java)
        mViewModel.count.observe(viewLifecycleOwner){ count->
            tv.text = count.toString()
        }
    }
}
class BottomFragment : Fragment() {

    private lateinit var  mViewModel : MainViewModel

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        return inflater.inflate(R.layout.fr_bottom,container,false)
    }
    override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)
        mViewModel = ViewModelProvider(requireActivity()).get(MainViewModel::class.java)
        btn.setOnClickListener {
            mViewModel.count.value = mViewModel.count.value?.plus(1)
        }
    }

}

需要注意的是,ViewModelProvider传入的是activity而不是当前的fragment,道理很简单,我们要实现Fragemnt之间的数据共享或者通讯,必须要拿到相同的ViewModel实例,如果传入this,那就不是同一个ViewModel对象了。

MainActivity:

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
    }

}

activity_main.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">

   <fragment
       android:id="@+id/frTop"
       android:layout_width="match_parent"
       android:layout_height="0dp"
       android:layout_weight="1"
       android:name="com.example.jetpack.TopFragment" />
    <View
        android:layout_width="match_parent"
        android:layout_height="2dp"
        android:background="#EEEEEE"/>

    <fragment
        android:id="@+id/frBottom"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:name="com.example.jetpack.BottomFragment" />

</LinearLayout>

在这个例子中,BottomFragment用于更新count的值并发布,TopFragment用于监听count的值并显示,实现效果:
在这里插入图片描述

就这样,TopFragment和BottomFragment完成了他们之间的通信,MainActivity却浑然不知。。。到这里,我们可以总结出ViewModel的两个作用:

  1. 保存UI数据
  2. Fragment通信

4.注意事项

在使用ViewModel时需要注意,由于ViewModel对象存在的时间比视图存在的时间长,所以不能持有对视图的引用(Context),否则可能会导致内存泄漏。如果你的ViewModel需要Application的上下文,可以继承AndroidViewModel。另外,如果你需要在ViewModel对象销毁前做回收工作,可以重写ViewModel的onCleared()方法。

5.总结

ViewModel的存在意义并不仅仅是为了管理UI数据,它能够有效的划分职责,正如它的名字一样,ViewModel提供了一个View(视图)和Model(数据模型)之间的桥梁,使得视图和数据能够分离开,也能够保持通信。

更多安卓知识体系,请关注公众号:
三分钟Code

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

一文带你了解ViewModel 的相关文章

随机推荐

  • 第四章 函数式编程(Lambda表达式&Stream流)

    一 Lambda表达式 特点 是匿名函数 2是可传递 匿名函数 需要一个函数 但又不想命名一个函数的场景下使用lambda表达式 使用lambda表达式时函数内容应该简单 可传递 将lambda表达式传递给其他的函数 它当做参数 lambd
  • 汽车安全标准ISO-26262以及等级ASIL

    1 什么是ISO 26262 为了保证即使出现部分电子器件故障 汽车系统也能在短期 故障容错时间内 内安全进行 2011年11月 ISO International Organization for Standardization 国际标准
  • 【12月海口】2022年第六届船舶,海洋与海事工程国际会议(NAOME 2022)

    2022年第六届船舶 海洋与海事工程国际会议 NAOME 2022 重要信息 会议网址 www icnaome org 会议时间 2022年12月23 25日 召开地点 海南 海口 截稿时间 2022年10月20日 录用通知 投稿后2周内
  • 剑指 Offer 68 - I. 二叉搜索树的最近公共祖先(java+python)

    给定一个二叉搜索树 找到该树中两个指定节点的最近公共祖先 百度百科中最近公共祖先的定义为 对于有根树 T 的两个结点 p q 最近公共祖先表示为一个结点 x 满足 x 是 p q 的祖先且 x 的深度尽可能大 一个节点也可以是它自己的祖先
  • go实现命令行拷贝文件

    package main import flag fmt bufio os strings io func FileExists dst string bool err os Stat dst return err nil os IsExi
  • docker命令、操作、部署服务器

    亲测有效 买了腾讯云 安装了centos8 0 进行docker操作 视频教学 https www bilibili com video BV1CJ411T7BK p 28 spm id from pageDriver vd source
  • Mysql驱动包下载

    第一步 下载地址 MySQL Download Connector J 第二步 第三步 第四步 解压 第五步 找到驱动包 放入项目使用即可
  • 知识图谱简介

    1 什么是知识图谱 知识图谱的概念是由谷歌公司于2012年5月17日首次提出 旨在描述客观世界的概念 实体 事件及其之间的关系 并作为构建下一代智能化搜索引擎的核心基础 通俗地讲 知识图谱就是把所有不同种类的信息连接在一起而得到的一个关系网
  • 如何最高效实现手机~电脑端文件传输?

    平常使用电脑办公的时候 经常会有把手机上的文件传到电脑或把电脑上的文件分发给局域网 内网 的各个伙伴的情况 通常我们会选择使用QQ或微信的文件传输功能来实现 但是当文件比较大 比较多时 就无法发送了 再者每次通过文件助手来发送文件时 其本质
  • 软件测试项目管理平台

    系统组成 STM软件测试项目管理系统采用C S软件架构 是一个多人协同工作的环境 数据库服务器端部署SQL Server数据库 包括人力资源数据库 设备资源数据库 项目管理数据库 测试项目数据库 历史归档数据库 客户端部署软件测试项目管理系
  • 从瞳孔的扩张收缩提取大脑EEG的delta,theta,alpha,beta,gamma等信号信息

    展示得到的结果图 直接上代码 import pandas as pd from scipy signal import find peaks from scipy fftpack import fft fftshift ifft impor
  • 【C语言刷LeetCode】300. 最长上升子序列(M)

    给定一个无序的整数数组 找到其中最长上升子序列的长度 示例 输入 10 9 2 5 3 7 101 18 输出 4 解释 最长的上升子序列是 2 3 7 101 它的长度是 4 说明 可能会有多种最长上升子序列的组合 你只需要输出对应的长度
  • (个人)AR电子书系统创新实训第二周(1)

    从头实现一个识别二维码的Unity项目 通过上次大致了解了ZXing Net的基本使用方法后 此次我决定使用它和unity制作一个简单的测试项目 以检验其功能是否满足要求 具体步骤如下 1 创建Unity项目 将zxing unity dl
  • 第十一届蓝桥杯 ——矩阵

    问题描述 把 1 2020 放在 2 1010 的矩阵里 要求同一行中右边的比左边大 同一列中下边的比上边的大 一共有多少种方案 答案很大 你只需要给出方案数除以 2020 的余数即可 答案提交 这是一道结果填空题 你只需要算出结果后提交即
  • 【DOS编程整理】

    以下文章来源 DOS编程大全 KingAntY的专栏 CSDN博客 dos编程 目录 第一章 批处理基础 第一节 常用批处理内部命令简介 1 REM 和 2 ECHO 和 3 PAUSE 暂停 4 ERRORLEVEL 5 TITLE 设置
  • C# 3D拾取技术,本地存储,角色控制器

    1 3D拾取技术 1 从原点发射一条射线 void Update 定义一条射线 起点为Vector3 zero 终点为物体坐标 Ray ray new Ray Vector3 zero transform position 定义一个光线投射
  • 给apk手动签名

    用Android studio生成签名文件然后用命令签名 先要对齐apk zipalign v p 4 需要签名apk 输出后的apk jarsigner verbose keystore 签名文件 jks storepass 密码 key
  • linux安装mysql 8.0.20(正式环境)

    安装之前需要确认机器上是否安装过mysql如果已经安装过 需要清理掉 1 检查是否已经安装过mysql rpm qa grep mysql 如果环境中有遗留mysql则执行删除命令 rpm e nodeps mysql xxxxxxxxx
  • 覆盖拦截器栈里特定拦截器的参数

  • 一文带你了解ViewModel

    Lifecycle库可以有效避免内存泄漏和解决常见的Android生命周期难题 1 引言 ViewModel属于lifecycle 生命周期感知型组件 中的一员 通常与LiveData DataBinding一起使用 它们是MVVM架构的重