Android ViewModel实现和原理

ViewModel实现和原理

  • 前言
  • 1. 使用
    • 1.1 gradle准备
    • 1.2 模拟场景
    • 1.3. LiveData和ViewModel
    • 1.4 更新数据
  • 2. 原理与源码解读
    • 2.1 添加观察者
    • 2.2 setValue
    • 2.3 post
  • 参考资料

前言

ViewModel的主要基于观察者的设计模式,他主要分为两个部分:

  1. 提供者Provider:在我们这里就是数据的提供者,当实体的数据改变的时候,会自动通知所有观察者,观察者收到通知后就可以做对应的数据。
  2. 观察者Observer:观察者注册提供程序,当提供者每次发送通知的时候,观察者就会做对应的处理。

其最终实现出来的效果就是,在代码中,一旦我们注册的实体对象里面的数据改变之后,对应的UI就会自动的进行更新,这样数据更新的代码我们只需要在观察者里面写一套,不需要反复写多套了。

提供者和观察者是一对多的关系,也就是一个提供者可以被很多个观察者注册

1. 使用

1.1 gradle准备

在用上ViewModel之前,需要在项目的build.gradle中加上如下内容,开启DataBinding。

为了写ui方便我把ViewBinding也加上了,他不是必须的,但是我的demo代码里面会有ViewBinding相关内容。

android {buildFeatures {dataBinding = trueviewBinding = true}
}

1.2 模拟场景

我们模拟一个简单的场景,页面里面就三个TextView,两个按钮,我们的目标就是用ViewModel来完成点了按钮之后他的Text就动态修改的功能。

实体是我随便设置的

class Student(var name: String = "",var age: Int = 0,var id: String = "")

页面长这样,三个TextView和两个按钮
在这里插入图片描述

1.3. LiveData和ViewModel

任何一个ViewModel的动态更新都需要围绕LiveData和ViewModel这两个类进行

LiveData:将实体动态化,可以被具有生命周期的对象观察到,只有这个目标对象的生命周期处于活动中,才会收到通知。
ViewModel:通信类,LiveData通过ViewModel来下发通知。

所以我们的代码最后写成这样:
ViewModel作为容器类,里面的成员变量是一个Student的LiveData类(一般都用MutableLiveData,如果有特殊需求也可以自己写)。
如果实际开发的时候有多个这样需要通信的实体,都丢到自定义的ViewModel类里面

class StudentViewModel : ViewModel() {private var student = MutableLiveData<Student>()fun getStudent(): MutableLiveData<Student> {return student}
}

注册通知的方式也很简单,先通过ViewModelProvider生成一个ViewModel的实体,然后将对应的实体进行观察者的注册即可。

lass ViewModelActivity : AppCompatActivity() {private lateinit var binding: ActivityViewModelBindingprivate lateinit var viewModel: StudentViewModel@SuppressLint("SetTextI18n")override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)binding = ActivityViewModelBinding.inflate(layoutInflater)setContentView(binding.root)// 创建ViewModel实体的固定写法viewModel = ViewModelProvider(this).get(StudentViewModel::class.java)// 注册Student对象,这样Student这个对象一旦改变,就会自动调用这里面的方法viewModel.getStudent().observe(this) {binding.vmName.text = "姓名:${it.name}"binding.vmAge.text = "年龄:${it.age}"binding.vmId.text = "id:${it.id}"}}
}

1.4 更新数据

更新数据有两种方式:
setValue:整个对象改变之后,他会自动的通知更新。
post:只改变目标对象里面的一两个成员变量时,通过Post进行更新。

class ViewModelActivity : AppCompatActivity() {private lateinit var binding: ActivityViewModelBindingprivate lateinit var viewModel: StudentViewModel@SuppressLint("SetTextI18n")override fun onCreate(savedInstanceState: Bundle?) {//上面有的代码就不贴了binding.vmBtn1.setOnClickListener {val student = Student(name = "张三", age = 16, id = "001")viewModel.setStudent(student)}binding.vmBtn2.setOnClickListener {viewModel.setName("李四")}}
}
class StudentViewModel : ViewModel() {private var student = MutableLiveData<Student>()//通过调用setValue进行更新,代码写成这样是因为kotlin语法省略//注意,setValue这个方法必须要在主线程进行fun setStudent(student: Student) {this.student.value = student}// 只更新局部成员变量,通过postValue进行更新fun setName(name: String) {this.student.value?.name = namestudent.postValue(student.value)}fun getStudent(): MutableLiveData<Student> {return student}
}

2. 原理与源码解读

这里先把ViewModel在代码层面上执行的原理先讲了,然后我们再通过源码看一下他具体是怎么实现的:

  1. 添加观察者就是在LiveData里面弄了一个HashMap,key是观察者的实体,value是观察者和生命周期对象的绑定类。
    等于是说我们实际上创建的这个观察者,即监听LiveData这个可以变动的实体,也监听了页面本身的生命周期。
  2. setValue就是当key更新了之后,就遍历这个HashMap的keySet,将生命周期状态为运行中的key,调用他们的回调。
  3. post就是通过handler来进行下方通知,所以post可以在子线程跑,其他的部分和setValue一样。

从他的这个原理我们也可以看到观察者模式这种设计模式的一般代码思路:

  1. 添加观察者就是找个集合,List,Set,HashMap等,把观察者对象装进去,观察者对象在注册的时候一般都会传入一个回调CallBack。
  2. 下发通知就是触发某个通知方法之后,遍历集合,然后挨个调用他们注册时传入的回调。

2.1 添加观察者

为了方便我们看源码,我先把当时我们调用observe的这行代码还原成Java的样子

	viewModel.getStudent().observe((LifecycleOwner)this, (Observer)(new Observer() {public void onChanged(Object var1) {this.onChanged((Student)var1);}public final void onChanged(Student it) {TextView var10000 = ViewModelActivity.access$getBinding$p(ViewModelActivity.this).vmName;Intrinsics.checkNotNullExpressionValue(var10000, "binding.vmName");var10000.setText((CharSequence)("姓名:" + it.getName()));var10000 = ViewModelActivity.access$getBinding$p(ViewModelActivity.this).vmAge;Intrinsics.checkNotNullExpressionValue(var10000, "binding.vmAge");var10000.setText((CharSequence)("年龄:" + it.getAge()));var10000 = ViewModelActivity.access$getBinding$p(ViewModelActivity.this).vmId;Intrinsics.checkNotNullExpressionValue(var10000, "binding.vmId");var10000.setText((CharSequence)("id:" + it.getId()));}}));

我们实际上是new了一个Observer对象,然后将这个对象作为入参传了进来。这个东西看着其实和我们的onClickListener之类的很像,其实就是个回调的监听。

public abstract class LiveData<T> {// 其实就是一个map,我们不用去关心他里面的具体代码原理,知道他是一个map,有和hashmap同款的功能即可private SafeIterableMap<Observer<? super T>, ObserverWrapper> mObservers =new SafeIterableMap<>();public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer) {// 主线程判断,不是主线程就抛出异常assertMainThread("observe");if (owner.getLifecycle().getCurrentState() == DESTROYED) {// 生命周期已经死亡的就无视return;}// 创建了另一个观察者类,这个观察者类是负责观察页面生命周期的LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer);// key为生命周期实体,value为观察者和页面生命周期的观察者类ObserverWrapper existing = mObservers.putIfAbsent(observer, wrapper);if (existing != null && !existing.isAttachedTo(owner)) {// 如果这个监听已经被注册过了,就会抛出异常throw new IllegalArgumentException("Cannot add the same observer"+ " with different lifecycles");}if (existing != null) {return;}// 将observer本身对页面的生命周期做监听owner.getLifecycle().addObserver(wrapper);}
}

mObservers就是我们所说的那个HashMap,key是观察者,也就是我们的new的那个observer类,value则是一个LifecycleBoundObserver对象,他是负责监听页面的生命周期的。

我们就通过这种方式即监听了LiveData本身,又监听了页面的生命周期。

2.2 setValue

接下来我们看看他是怎么做到动态更新的。从setValue这个方法开始,为了方便看,这里省略一些和原理无关的逻辑代码。

public abstract class LiveData<T> {@MainThreadprotected void setValue(T value) {// setValue方法也必须在主线程执行assertMainThread("setValue");dispatchingValue(null);}void dispatchingValue(@Nullable ObserverWrapper initiator) {do {if (initiator != null) {//省略代码,我们入参是null } else {for (Iterator<Map.Entry<Observer<? super T>, ObserverWrapper>> iterator =mObservers.iteratorWithAdditions(); iterator.hasNext(); ) {// 重点看这行,这里就是遍历所有的监听执行considerNotify(iterator.next().getValue());if (mDispatchInvalidated) {break;}}}} while (mDispatchInvalidated);}private void considerNotify(ObserverWrapper observer) {// 页面生命周期判断,不符合就return了if (!observer.shouldBeActive()) {observer.activeStateChanged(false);return;}// 数据的版本号判断,版本号不符合就return了if (observer.mLastVersion >= mVersion) {return;}observer.mLastVersion = mVersion;// 调用onChanged,onChanged就是我们的回调方法observer.mObserver.onChanged((T) mData);}
}

这样一看逻辑就很清晰了,每次我们调用setValue,他就会跑一个for循环,把所有的observer都做一个检测,符合要求的就调用最后的onChanged,不符合的就不调用。

2.3 post

我们最后在过一下post这条线的更新逻辑,这边本质上逻辑也是一样的

public abstract class LiveData<T> {volatile Object mPendingData = NOT_SET;private final Runnable mPostValueRunnable = new Runnable() {@SuppressWarnings("unchecked")@Overridepublic void run() {Object newValue;synchronized (mDataLock) {newValue = mPendingData;mPendingData = NOT_SET;}// 本质上还是调用setValue,回到2.2了setValue((T) newValue);}};protected void postValue(T value) {boolean postTask;synchronized (mDataLock) {postTask = mPendingData == NOT_SET;mPendingData = value;}if (!postTask) {return;}// 无视掉同步锁的那些代码,本质就是跑了这一行,这个方法从名字上就知道是把一个东西post到主线程执行ArchTaskExecutor.getInstance().postToMainThread(mPostValueRunnable);}
}

代码一眼看到头,通过一个线程池将一个runnable推到主线程去处理,最后还是调用的setValue。

最后看一眼他这个线程池是怎么推到主线程的。

public class ArchTaskExecutor extends TaskExecutor {// TaskExecutor是个抽象类,实现是DefaultTaskExecutorprivate TaskExecutor mDelegate;private ArchTaskExecutor() {mDefaultTaskExecutor = new DefaultTaskExecutor();mDelegate = mDefaultTaskExecutor; }@Overridepublic void postToMainThread(@NonNull Runnable runnable) {mDelegate.postToMainThread(runnable);}
}public class DefaultTaskExecutor extends TaskExecutor {private volatile Handler mMainHandler;public void postToMainThread(@NonNull Runnable runnable) {if (mMainHandler == null) {synchronized (mLock) {if (mMainHandler == null) {mMainHandler = createAsync(Looper.getMainLooper());}}}//noinspection ConstantConditionsmMainHandler.post(runnable);}
}

参考资料

https://developer.android.google.cn/topic/libraries/architecture/viewmodel/viewmodel-factories?hl=zh-cn

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

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

相关文章

【React】极客园--01.项目前置准备

项目搭建 基于CRA创建项目 CRA是一个底层基于webpack快速创建React项目的脚手架工具 # 使用npx创建项目 npx create-react-app react-jike# 进入到项 cd react-jike# 启动项目 npm start调整项目目录结构 -src-apis 项目接口函数-assets 项目资源文件&…

CSS【实战】抽屉动画

效果预览 技术要点 实现思路 元素固定布局&#xff08;fixed&#xff09;在窗口最右侧外部js 定时器改变元素的 right 属性&#xff0c;控制元素移入&#xff0c;移出 过渡动画 transition transition: 过渡的属性 过渡的持续时间 过渡时间函数 延迟时间此处改变的是 right …

shell脚本之函数

一、1.函数&#xff1a;将命令序列按照固定的表达格式写在一起 2.函数作用&#xff1a;可以重复使用的命令序列&#xff0c;大的工程分割成若干个小块&#xff0c;依次执行&#xff0c;提高代码的可读性。 3.函数的两种格式 4.return作用&#xff1a;只能写在函数内部&#xff…

26 红黑树

目录 1.概念 2.性质 3.节点定义 4.结构 5.插入 6.验证 7.删除 8.红黑树和avl树比较 9.应用 概念 是一种二叉搜索树&#xff0c;但在每个节点上增加一个存储位表示节点的颜色&#xff0c;可以是red或black。通过对任何一条从根到叶子的路径上各个节点着色方式的限制&#xff…

reverse-android-实战喜马拉雅-ollvm

资料 1. apk: com.ximalaya.ting.android.apk. 2020年8月 可以使用 2. 抓包分析 java层分析 so层分析 登录的算法so是在 liblogin_encrypt.so中。 32位的&#xff0c; 用 IDA打开&#xff0c;查看 静态的导出函数。 打开 一个 首先看到 IDA VIEW 是一个横向 比较多的分支&am…

2-9 基于matlab的传递矩阵计算轴的模态

基于matlab的传递矩阵计算轴的模态&#xff0c;包括模态频率和模态振型&#xff0c;可设置轴的结构参数。程序已调通&#xff0c;可直接运行。 2-9 传递矩阵计算轴的模态 模态频率 - 小红书 (xiaohongshu.com)

python-jupyter notebook安装教程

&#x1f308;所属专栏&#xff1a;【python】✨作者主页&#xff1a; Mr.Zwq✔️个人简介&#xff1a;一个正在努力学技术的Python领域创作者&#xff0c;擅长爬虫&#xff0c;逆向&#xff0c;全栈方向&#xff0c;专注基础和实战分享&#xff0c;欢迎咨询&#xff01; 您的…

Jmeter 逻辑控制之IF条件控制器

&#x1f345; 视频学习&#xff1a;文末有免费的配套视频可观看 &#x1f345; 点击文末小卡片 &#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 测试环境 JMeter-5.4.1 循环控制器介绍 添加While Controller 右键线程组->添加->逻辑…

简易计算器需求报告

1. &#xff08;简易计算器&#xff09; 需求说明书 文件编号&#xff1a;2022[1] [木柚2] 06[3] [木柚4] 01[5] [木柚6] 完成日期&#xff1a;2024年 06月18日 编制&#xff1a; 易正阳 日期&#xff1a;2024年6月18日 审核&#xff1a;张正 日期&#xff1a;2024年6月18…

Vue微前端架构与Qiankun实践理论指南

title: Vue微前端架构与Qiankun实践理论指南 date: 2024/6/15 updated: 2024/6/15 author: cmdragon excerpt: 这篇文章介绍了微前端架构概念&#xff0c;聚焦于如何在Vue.js项目中应用Qiankun框架实现模块化和组件化&#xff0c;以达到高效开发和维护的目的。讨论了Qiankun…

软件测试面试题:性能测试关注哪些指标?

问题 在工作中&#xff0c;使用JMeter做压力测试时&#xff0c;需要关注其中的哪些指标&#xff1f; 性能测试关注哪些指标&#xff1f; 考察点 面试官想了解&#xff1a; 是否用过 JMeter 指标进行分析 技术点 涉及的技术点&#xff1a; JMeter 结果分析 回答 性能指…

gitblit git pycharm 新建版本库及push备忘

在终端l中输入ssh,如果有消息弹出说明安装成功。 // 在任意路径打开GIT BASH,执行以下命令,期间所有询问可以直接Enter跳过 ssh-keygen -t rsa -C "注册Gitlab的邮箱" “”之内可以任何文字,备注提示作用。 设置用户名和邮箱 已经设置的可以检查一下。 #设置用…

SpringBoot配置第三方专业缓存框架j2cache

j2cache的使用 这不是一个缓存 这是一个缓存框架 J2Cache, 也称为Java Cache或JSR-107&#xff0c;是一个用于缓存管理的标准API&#xff0c;它允许开发者在Java应用程序中实现分布式、基于内存的缓存。J2Cache主要通过javax.cache.Cache接口提供功能&#xff0c;用于存储和…

RoaringBitMap处理海量数据内存diff

一、背景 假设mysql库中有一张近千万的客户信息表(未分表)&#xff0c;其中有客户性别&#xff0c;等级(10个等级)&#xff0c;参与某某活动等字段 1、如果要通过等级性别其他条件(离散度也低)筛选出客户&#xff0c;如何处理查询&#xff1f; 2、参与活动是记录活动ID&#…

了解Nest.js

一直做前端开发&#xff0c;都会有成为全栈工程师的想法&#xff0c;而 Nest 就是一个很好的途径&#xff0c;它是 Node 最流行的企业级开发框架&#xff0c;提供了 IOC、AOP、微服务等架构特性。接下来就让我们一起来学习Nest.js Nest.js官网地址 一&#xff0c;了解Nest Cli …

充电学习—6、电量计FuelGauge

电量计功能&#xff1a; 检测电池 计量电量 电量计首要工作&#xff1a; 计算电池的剩余容量、充满时容量、电量百分比 电量百分比 剩余容量 / 充满时容量 * 100% SOC RM / FCC * 100% 典型的一个电池包框架&#xff1a; 包含电芯、电量计IC、保护IC、充放电MOSFET、保险丝…

栈帧浅析,堆栈漏洞概述——【太原理工大学软件安全期末补充】

在上一篇文章中我说实验一不重要&#xff0c;确实没必要完全按照实验内容逐字逐句理解&#xff0c;但是这里我们补充一个知识点 栈帧&#xff08;Stack Frame&#xff09;是计算机程序执行过程中&#xff0c;调用栈&#xff08;Call Stack&#xff09;中的一个单元&#xff0c;…

hugging face:大模型时代的github介绍

1. Hugging Face是什么&#xff1a; Hugging Face大模型时代的“github”&#xff0c;很多人有个这样的认知&#xff0c;但是我觉得不完全准确&#xff0c;他们相似的地方在于资源丰富&#xff0c;github有各种各样的软件代码和示例&#xff0c;但是它不是系统的&#xff0c;没…

Linux-DNS域名解析服务01

BIND 域名服务基础 1、DNS&#xff08;Domain Name System&#xff09;系统的作用及类型 整个 Internet 大家庭中连接了数以亿计的服务器、个人主机&#xff0c;其中大部分的网站、邮件等服务器都使用了域名形式的地址&#xff0c;如 www.google.com、mail.163.com 等。很显然…

探索C嘎嘎的奇妙世界:第十四关---STL(string的模拟实现)

1. string类的模拟实现 1.1 经典的string类问题 上一关已经对string类进行了简单的介绍&#xff0c;大家只要能够正常使用即可。在面试中&#xff0c;面试官总喜欢让学生自己来模拟实现string类&#xff0c;最主要是实现string类的构造、拷贝构造、赋值运算符重载以及析构函数…