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的方法,这个方法的内部实现就是有两个步骤。

  1. 调用ViewModelStore的get方法查询是否有创建好的对象,如果有就返回,方法结束,否则进入步骤2。

  2. 调用ViewModelProvider.Factorycreate方法创建对象,并将之保存到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的更多相关文章

  1. v77.01 鸿蒙内核源码分析(消息封装篇) | 剖析LiteIpc(上)进程通讯内容 | 新的一年祝大家生龙活虎 虎虎生威

    百篇博客分析|本篇为:(消息封装篇) | 剖析LiteIpc进程通讯内容 进程通讯相关篇为: v26.08 鸿蒙内核源码分析(自旋锁) | 当立贞节牌坊的好同志 v27.05 鸿蒙内核源码分析(互斥锁 ...

  2. v78.01 鸿蒙内核源码分析(消息映射篇) | 剖析LiteIpc(下)进程通讯机制 | 百篇博客分析OpenHarmony源码

    百篇博客分析|本篇为:(消息映射篇) | 剖析LiteIpc(下)进程通讯机制 进程通讯相关篇为: v26.08 鸿蒙内核源码分析(自旋锁) | 当立贞节牌坊的好同志 v27.05 鸿蒙内核源码分析( ...

  3. Android Jetpack组件 - ViewModel,LiveData使用以及原理

    本文涉及的源码版本如下: com.android.support:appcompat-v7:27.1.1 android.arch.lifecycle:extensions:1.1.1 android ...

  4. 看过这篇剖析,你还不懂 Go sync.Map 吗?

    hi, 大家好,我是 haohongfan. 本篇文章会从使用方式和原码角度剖析 sync.Map.不过不管是日常开发还是开源项目中,好像 sync.Map 并没有得到很好的利用,大家还是习惯使用 M ...

  5. Jetpack的ViewModel与LiveData

    本文基于SDK 29 一.ViewModel与LiveData的作用: 1.viewModel: 数据共享,屏幕旋转不丢失数据,并且在Activity与Fragment之间共享数据. 2.LiveDa ...

  6. Jetpack Compse 实战 —— 全新的开发体验

    公众号回复 Compose 获取安装包 项目地址: Wanandroid-Compose 经过前段时间的 Android Dev Summit ,相信你已经大概了解了 Jetpack Compose ...

  7. Android  JetPack~ LiveData (一)   介绍与使用

    一般情况下LiveData都是搭配这ViewModel使用,这里先介绍一下LiveData,再结合ViewModel使用 Android数据绑定技术一,企业级开发 Android数据绑定技术二,企业级 ...

  8. WPF基础到企业应用系列7——深入剖析依赖属性(WPF/Silverlight核心)

    一. 摘要 首先圣殿骑士非常高兴这个系列能得到大家的关注和支持.这个系列从七月份開始到如今才第七篇,上一篇公布是在8月2日,掐指一算有二十多天没有继续更新了,最主要原因一来是想把它写好,二来是由于近期 ...

  9. 大数据理论篇 - 通俗易懂,揭秘谷歌《The Dataflow Model》的核心思想(一)

    目录 前言 目标 核心的设计原则 通用的数据处理流程 切合实际的解决方案 总结 延伸阅读 最后 作者:justmine 头条号:大数据达摩院 创作不易,未经授权,禁止转载,否则保留追究法律责任的权利. ...

  10. 反病毒攻防研究第006篇:简单木马分析与防范part2

    一.前言 一般来说,木马是既有客户端也有服务器端的.上次讨论的不过是一种特殊情况,毕竟不是人人都懂得DOS命令,因此现在木马的客户端也都是做成非常直观的界面形式,方便操作.本篇文章会从客户端与服务器端 ...

随机推荐

  1. Little Snitch 5 - Mac 老牌防火墙安全工具软件小飞贼,监控和组织特定软件的网络连接

    一旦连接到Internet,应用程序就可以随时随地发送它们想要的任何东西.通常情况下,他们是为你做的.但有时,例如在跟踪软件.木马或其他恶意软件的情况下,它们不会.但你不会注意到任何东西,因为所有这些 ...

  2. 教你如何通过CodeArts IDE插件调用API,高效合成语音

    摘要:本实验基于华为云自研CodeArts IDE,指导用户通过使用华为云API,来实现一个文字合成语音的应用. 本文分享自华为云社区<通过CodeArts IDE插件调用API,高效合成语音! ...

  3. 1、kubernetes概念

    一.kubernetes简介 kubernetes的本质是一组服务器集群.它可以在每个节点上运行特定的程序,来对节点中的容器进行管理.目的是为了实现资源管理的自动化. 主要功能如下: 1.自我修复:一 ...

  4. 二进制安装Kubernetes(k8s) v1.26.0 IPv4/IPv6双栈

    二进制安装Kubernetes(k8s) v1.26.0 IPv4/IPv6双栈 https://github.com/cby-chen/Kubernetes 开源不易,帮忙点个star,谢谢了 介绍 ...

  5. 在Blazor中使用Chart.js

    1. 在Blazor中使用Chart.js 首先,从Chart.js官方网站下载Chart.js库文件. 推荐下载这个构建好的版本https://cdnjs.com/libraries/Chart.j ...

  6. yiiADU

    <span style="font-size:14px;">一. //1.该方法是根据一个条件查询一个集合 $admin=Admin::model()->find ...

  7. Redis分布式锁这样用,有坑?

    背景 在微服务项目中,大家都会去使用到分布式锁,一般也是使用Redis去实现,使用RedisTemplate.Redisson.RedisLockRegistry都行,公司的项目中,使用的是Redis ...

  8. Docker MariaDB配置主从复制

    编写主节点配置文件master.cnf: [client] # 默认字符集 default-character-set=utf8mb4 [mysqld] # 字符集 character-set-ser ...

  9. 飞桨paddlespeech语音唤醒推理C实现

    上篇(飞桨paddlespeech 语音唤醒初探)初探了paddlespeech下的语音唤醒方案,通过调试也搞清楚了里面的细节.因为是python 下的,不能直接部署,要想在嵌入式上部署需要有C下的推 ...

  10. HDCTF_2023

    pwnner 附件 有后门函数,seed是一个固定值, //伪随机数 #include <stdio.h> #include <stdlib.h> int main() { i ...