沉思篇-剖析Jetpack的ViewModel
ViewModel做为架构组件的三元老之一,是实现MVVM的有力武器。
ViewModel的设计目标
ViewModel的基本功能就是管理UI的数据。其实,从职责上来说,这又是对Activity和Fragment的一次功能拆分。以前存储在它们内部的数据,需要它们自己处理创建,更新,存储,恢复的所有过程,同时它们还要处理UI的数据绑定,更新,动画等操作。职责的多元化就容易出现不好定位和调试的问题。另外,Activity和Fragment作为UI的承载者,很多时候需要共享数据和复用功能。而UI的差异让复用的粒度划分很难把控,容易写出扩展性差的代码。基于这些痛点,ViewModel被设计出来了。
同时ViewModel还将保存数据的功能强化了——它将设备配置变更后数据保存和恢复自动化了,在UI生命周期内都能保证数据的有效性,这大大减少了样板代码的编写,提高了开发效率。
ViewModel的架构设计
ViewModel用了两种粒度划分来完成数据管理功能。 第一层是对ViewModel自身存储数据的管理。目标就是完成ViewModel的创建,对应的抽象实体是ViewModelProvider.Factory。第二层则是对已存在的ViewModel组的管理,目标就是保证意外情况下ViewModel的有效性,对应的抽象实体是ViewModelStore。当然,这些都只是概念上的抽象,还需要一个粘合剂把它们的抽象层级体现出来,这就是ViewModelProvider。这三个主体类共同搭建了ViewModel的体系框架。剩下的类都是对这三个概念的补充和完善。接下来我将分别以这些抽象为主线,逐层分析它们的实现逻辑。
ViewModel的组管理
前面也提到过,ViewModelStore是完成组管理的,那么我们首先应该确定的是组的概念,也就是这些ViewModel都归属于谁的问题。这不难理解,要管理组,那就必须得找到组的主人啊,由此引申出了ViewModelStoreOwner,它代表着某个拥有组管理权限的对象,通过它提供的ViewModelStore对象就能对里面的ViewModel进行管理了,同时这些ViewModel也就共同形成了组。所以ViewModelStoreOwner其实就是组的抽象实体,它代表着某个组,也是管理分组的单位。
ViewModel有两个默认实现的组——ComponentActivity和Fragment。也就是说ComponentActivity和Fragment都实现了ViewModelStoreOwner这个接口。
先来看ComponentActivity的实现。根据接口,首先查看接口方法getViewModelStore的实现。里面主要涉及到两个对象,一个就是ViewModelStore的引用mViewModelStore,另一个就很有意思了,它是一个NonConfigurationInstances对象,这是一个简单类,就是保存ViewModelStore对象的。那么它特殊在什么地方呢?它是onRetainNonConfigurationInstance方法的返回对象。
插一个课外知识科普,
onRetainNonConfigurationInstance是Activity的一个方法,这个方法是设备配置发生变化(如横竖屏切换的时候)时被系统自动调用的,用于用户保存数据。只要这个方法返回的对象,在设备配置放生改变时都不会被销毁。稍后在重建完成后,可以通过getLastNonConfigurationInstance方法获取到。
接着回到getViewModelStore的实现,刚才说到NonConfigurationInstances对象,它是通过调用getLastNonConfigurationInstance方法获得的。如果方法返回了有效的对象,说明Activity被重建了,就直接获取保存在NonConfigurationInstances对象中的值,然后更新mViewModelStore。否则就说明还没有有效的ViewModelStore对象,则直接创建。从这个逻辑不难看出,我们的ViewModel不会随着设备变化而重建,这正好满足了我们的设计目标。那么对于Fragment,它的实现又是怎样的呢。
Fragment的实现比较曲折,它直接委托给了FragmentManager,又委托给了FragmentManagerViewModel的getViewModelStore方法,方法实现也很简单,就是对HashMap查找,没有就创建新的。这显然不是我们想看到的,因为这里并没有和Activity类似的处理状态变更的逻辑。那么唯一的突破点就是那个HashMap对象了。搜索一圈发现,它会作为getSnapshot方法的返回值返回,有点Activity那味了。往上回溯,会发现它最终就是作为不销毁的对象,在Fragment销毁前保存下来了。
以上就是两种应用场景下ViewModelStore的创建逻辑,另外,还有清除逻辑没有讲到。这个逻辑本质上就是调用ViewModelStore的clear方法,唯一的问题就是确定调用时机。具体来说就是,Activity通过注册Lifecycle的状态监听,在Lifecycle.Event.ON_DESTROY的时候,调用了clear方法,而Fragment则是继续通过FragmentManager的desctory方法作为调用的入口点。在FragmentManagerViewModel里完成了方法调用。
总结一下,ViewModelStoreOwner是对ViewModel组的一种抽象。虽然对应着两个不同的实现,但是殊途同归,最终的目的就是保证在设备配置发生变化的时候对应ViewModelStore对象的有效性, 从而保证ViewModel对象的有效性。同时在真正需要销毁的时候做好清理工作。这就是这ViewModel的组管理功能。
ViewModel的创建管理
ViewModel用ViewModelProvider.Factory来管理创建过程。具体来说就是怎样根据一个ViewModel子类的类信息创建对应的对象。这有两个难点——必要的依赖注入、数据的恢复。对于依赖注入,ViewModel还是耍了老把戏,和创建ViewModelStore类似,提供了HasDefaultViewModelProviderFactory的一个抽象,把依赖注入转移到了ComponentActivity和Fragment中。之所以这么做,是因为在创建ViewModel的过程中,可能需要使用到Application和Bundle等信息,而这些信息是只能在在Activity和Fragment中才能获取到的。数据恢复则是关注怎样利用现有的数据将对象恢复到原来的状态。当然这些过程其实都可以没有,不需要传递Application或者Bundle对象,不需要恢复ViewModel状态,则库提供了默认的实现。就是简单的调用反射创建对象而已。
针对刚才说的各种情况,ViewModelProvider.Factory有多个实现,那么实际上它到底是使用哪个实现呢,我们得从ViewModelProvider中寻找答案。在它的构造方法里,会对ViewModelStoreOwner做类型判断,假如它是HasDefaultViewModelProviderFactory的实例,则使用实例返回的对象,否则默认的实现。结合上面的分析,让我们继续到ComponentActivity和Fragment中寻找答案。不看不知道,一看吓一跳,它们竟然都是使用了SavedStateViewModelFactory类,那么我们一起来看看它是怎么实现的吧。
在构建SavedStateViewModelFactory对象的时候,会传入三个对象——Application,SavedStateRegistryOwner,Bundle,这三个对象中最重要的就是第二个,它的主要功能就是提供在SavedStateRegistry对象,这个对象会在合适的时候保存数据,然后在合适的时候再恢复过来。它也是生命周期感知的组件。在它的create方法里,也是通过反射构建ViewModel对象的,唯一的不同就是反射多了个参数。接着往下看,最终会利用这些信息构造出SavedStateHandle对象,这个对象就是真正对我们当前创建的ViewModel对象有用的信息。SavedStateHandle提供了根据键值对保存数据的方法,也提供了查询方法,所以ViewModel可以根据这个对象,恢复自己的LiveData数据,最重要的,这个类还提供了LiveData的另一个子类SavingStateLiveData,能自动处理数据保存的问题。
一句话总结,ComponentActivity和Fragment会使用SavedStateViewModelFactory对象作为ViewModelProvider中的Factory来创建ViewModel。只要ViewModel提供了带有Application或者SavedStateHandle的构造方法,就能享受从Bundle中恢复数据的便利。
ViewModel的粘合剂ViewModelProvider
为什么说ViewModelProvider是粘合剂呢?因为这个类就做了一件事,把ViewModelStore和ViewModelProvider.Factory组合起来,实现了一个叫get的方法,这个方法的内部实现就是有两个步骤。
调用ViewModelStore的
get方法查询是否有创建好的对象,如果有就返回,方法结束,否则进入步骤2。调用
ViewModelProvider.Factory的create方法创建对象,并将之保存到ViewModelStore中。
所以当我们要使用ViewModel的时候,通常是创建ViewModelProvider对象,然后调用get方法获取真正的ViewModel对象,这样,我们的对象就具备了正确处理设备配置变更的能力。
ViewModel的Fragment间通信功能
根据前面的梳理,我们知道,ViewModelStore是管理某个ViewModel组的,只要我们保证ViewModelStore存在,我们就可以保证ViewModel存活。再反推一步,要保证ViewModelStore存活,我们就要保证ViewModelStoreOwner在不同的地方都能返回同一个ViewModelStore对象,而ComponentActivity和Fragment是都实现了这个接口的。结合Activity的生命周期通常是大于Fragment这一事实,不难得出结论——在某个Fragment里面,用Activity对象创建ViewModelProvider对象,就能保证获取到和Activity一样的ViewModelStore对象,也就能保证获取到相同的ViewModel对象。只要Activity没有销毁,该Activity下的所有Fragment都能获取到相同的ViewModel对象,然后通过更改状态能方式完成通信。
到此,对ViewModel的分析告一段落了,对创建过程的两次抽象是我觉得最精彩的环节,另外对现有条件(Activity和Fragment的生命周期)的利用也是它独到之处,真的是受益匪浅。青山不改,绿水长流,咱们下期见!

沉思篇-剖析Jetpack的ViewModel的更多相关文章
- v77.01 鸿蒙内核源码分析(消息封装篇) | 剖析LiteIpc(上)进程通讯内容 | 新的一年祝大家生龙活虎 虎虎生威
百篇博客分析|本篇为:(消息封装篇) | 剖析LiteIpc进程通讯内容 进程通讯相关篇为: v26.08 鸿蒙内核源码分析(自旋锁) | 当立贞节牌坊的好同志 v27.05 鸿蒙内核源码分析(互斥锁 ...
- v78.01 鸿蒙内核源码分析(消息映射篇) | 剖析LiteIpc(下)进程通讯机制 | 百篇博客分析OpenHarmony源码
百篇博客分析|本篇为:(消息映射篇) | 剖析LiteIpc(下)进程通讯机制 进程通讯相关篇为: v26.08 鸿蒙内核源码分析(自旋锁) | 当立贞节牌坊的好同志 v27.05 鸿蒙内核源码分析( ...
- Android Jetpack组件 - ViewModel,LiveData使用以及原理
本文涉及的源码版本如下: com.android.support:appcompat-v7:27.1.1 android.arch.lifecycle:extensions:1.1.1 android ...
- 看过这篇剖析,你还不懂 Go sync.Map 吗?
hi, 大家好,我是 haohongfan. 本篇文章会从使用方式和原码角度剖析 sync.Map.不过不管是日常开发还是开源项目中,好像 sync.Map 并没有得到很好的利用,大家还是习惯使用 M ...
- Jetpack的ViewModel与LiveData
本文基于SDK 29 一.ViewModel与LiveData的作用: 1.viewModel: 数据共享,屏幕旋转不丢失数据,并且在Activity与Fragment之间共享数据. 2.LiveDa ...
- Jetpack Compse 实战 —— 全新的开发体验
公众号回复 Compose 获取安装包 项目地址: Wanandroid-Compose 经过前段时间的 Android Dev Summit ,相信你已经大概了解了 Jetpack Compose ...
- Android JetPack~ LiveData (一) 介绍与使用
一般情况下LiveData都是搭配这ViewModel使用,这里先介绍一下LiveData,再结合ViewModel使用 Android数据绑定技术一,企业级开发 Android数据绑定技术二,企业级 ...
- WPF基础到企业应用系列7——深入剖析依赖属性(WPF/Silverlight核心)
一. 摘要 首先圣殿骑士非常高兴这个系列能得到大家的关注和支持.这个系列从七月份開始到如今才第七篇,上一篇公布是在8月2日,掐指一算有二十多天没有继续更新了,最主要原因一来是想把它写好,二来是由于近期 ...
- 大数据理论篇 - 通俗易懂,揭秘谷歌《The Dataflow Model》的核心思想(一)
目录 前言 目标 核心的设计原则 通用的数据处理流程 切合实际的解决方案 总结 延伸阅读 最后 作者:justmine 头条号:大数据达摩院 创作不易,未经授权,禁止转载,否则保留追究法律责任的权利. ...
- 反病毒攻防研究第006篇:简单木马分析与防范part2
一.前言 一般来说,木马是既有客户端也有服务器端的.上次讨论的不过是一种特殊情况,毕竟不是人人都懂得DOS命令,因此现在木马的客户端也都是做成非常直观的界面形式,方便操作.本篇文章会从客户端与服务器端 ...
随机推荐
- Javascript 加密解密方法
本文链接 https://www.cnblogs.com/zichliang/p/17265960.html Javascript 和 我之前发的 python加密 以及 go加密 解密不一样 不需要 ...
- 游戏AI——GOAP技术要点
目录 什么是GOAP(Goal-Oriented Action Planning) 介绍 细节 难点与挑战 世界表达 具体类型表示 字符串表示 bool转化为枚举 规划器 Regoap流程 Middl ...
- JSTL标签fmt:formatDate格式化日期出错
现象&背景: 异常: "org.apache.jasper.JasperException: 在 [115] 行处理 [/WEB-INF/jsp/modules/receivedya ...
- pysimplegui之调试输出(easy_print = Print = eprint)
"Easy" API 系列中的另一个调用是EasyPrint. 与其他常用的 PySimpleGUI 调用一样,同一个调用还有其他名称.您可以使用Print或eprint除了Eas ...
- 手机号码归属地 API 实现个性化推荐的思路分析
前言 随着移动互联网和智能手机的普及,越来越多的人使用手机上网和购物,移动营销已成为企业获取用户和提升品牌知名度的重要手段.手机号码归属地 API 作为移动营销的关键工具,具有广阔的应用前景. 本文将 ...
- MySQL匿名空用户名处理
问题描述:公司漏扫发现数据库内出现空用户名及密码,需要对这些用户进行整改 1.首先出现了疑问,这些空的用户名是怎么出现的,而且不附带密码. 2.可以手动这样创建这样的用户名和密码形式么. 3.如果能这 ...
- MINIO使用
1.作用 官网地址:https://docs.min.io/ 文件存储.文件对象的上传.下载和删除! 2.使用依赖 <dependency> <groupId>io.minio ...
- docker上面部署nginx-waf 防火墙“modsecurity”,使用CRS规则,搭建WEB应用防火墙
web防火墙(waf)免费开源的比较少,并且真正可以商用的WAF少之又少,modsecurity 是开源防火墙鼻祖并且有正规公司在维护着,目前是https://www.trustwave.com在维护 ...
- 轻量化3D文件格式转换HOOPS Exchange新特性
BIM与AEC市场发展现状 近年来BIM(建筑信息模型)和AEC(建筑.工程和施工)市场一直保持着持续增长.2014 年全球 BIM 软件市场价值 27.6 亿美元,而到 2022年,预期到达115. ...
- python pyinstaller库
简要 pyinstaller模块主要用于python代码打包成exe程序直接使用,这样在其它电脑上即使没有python环境也是可以运行的. 用法 一.安装 pyinstaller属于第三方库,因此在使 ...