Jetpack学习-ViewModel
什么是ViewModel?
谷歌对ViewModel的介绍是这样的: ViewModel类是被设计用来以可感知生命周期的方式存储和管理 UI 相关数据, ViewModel中数据会一直存活即使 activity configuration发生变化, 比如横竖屏切换的时候.
实际上, 我们可以这么理解ViewModel: 它是一个更高层次的仓库. 这个仓库与创建它的Activity或者Fragment绑定, 当Activity或者Fragment彻底死亡后, 这个仓库才会死亡.
上图是我们经常看到的ViewModel生命周期的一个展示. 可以看到, ViewModel并不是只要Activity进入onDestroy()阶段就会被销毁——只有Activity通过finish进入onDestroy()阶段(也就是调用finish()方法的时候), 即Activity彻底被销毁后, 与之绑定的ViewModel才会被销毁(对于Fragment来说, 是当Fragment进入onDetach()的时候).
ViewModel的使用
因为ViewModel只是一个容器, 它在系统中不是单独出现的, 所以这篇文章并不打算去展示一些场景的代码, 而是围绕着ViewModel的核心特点——一个伴随着创建者一生的容器类这一点, 来讨论什么时候可以使用ViewModel, 使用它的时候应该注意什么.
数据与UI的解耦
针对ViewModel的核心特点, 我们首先能想到的是, 让ViewModel成为一个数据的容器. 在ViewModel出现之前, Activity和Fragment不仅需要控制UI(比如一长串的findViewById以及随之而来的各种setOnClickListener), 还需要保存数据, 这使得作为UI Controller的Activity和Fragment承担了过多的任务, 违背了单一职责原则. 引入ViewModel后, 由于ViewModel的生命周期和Activity或者Fragment基本相同(实际上略长, 因为ViewModel是在观察的Lifecycle销毁后才销毁自己的), 我们可以将数据转存到ViewModel中, 这样虽然逻辑上数据和Activity是在一起的, 但是实际上我们将数据管理的职责分配给了ViewModel, 这样就实现了数据与UI的解耦.
数据生命周期的延长
Activity和Fragment存储数据还会带来另一个问题: 当Activity重新创建时, 之前所存储的数据都会消失, 毕竟保存它们的类已经被销毁过了. 在ViewModel出现之前, 我们都是通过一个Bundle来保存数据, 等Activity或者Fragment调用onCreate时, 再从Bundle中取出数据. 这种方式是有一定局限的——Bundle只能存储基本数据类型和可序列化的类. 现在有了ViewModel, 当Activity或者Fragment销毁重建的时候, 因为ViewModel所观察的Lifecycle并没有被销毁, 所以它仍然存在. 这样新的Activity或者Fragment实例就可以通过ViewModel重新加载之前的数据, 这些数据即使不可序列化也没有关系了.
数据的共享
由于一个ViewModel是伴随着创建者的一生的, 这意味着如果创建者没有彻底销毁, 那么ViewModel应该是始终存在且唯一的, 我们可以把这种特性称为“伪单例”. 从这个性质出发, 我们很容易想到ViewModel可以用来实现数据的共享. 我们来看一下使用ViewModel的代码:
1 | ViewModel viewModel = new ViewModelProvider(this).get(ViewModel.class); |
这里的”this”, 一般就是Activity或者Fragment的自引用. 从这句话中我们可以看到, 我们能获得什么样的ViewModel, 取决于我们在ViewModelProvider的构造函数中和get方法中传入什么样的值. 换句话说, 只要传的值一样, 最终获取到的ViewModel就是一样的. 这样一来, 如果同一个Activity下的两个Fragment想要共享数据, 那么它们只需要通过与宿主Activity绑定的ViewModel来通信就可以了.
异步请求的处理
这一点可能比较难想到. 一般来说, APP所展示的数据都是我们通过网络向服务器请求来的. 因为安卓是不允许在主线程进行网络请求的, 所以所有的网络通信都是异步的, 这就会带来一个问题: 在极少数情况下可能会出现Acitivty刚刚发出请求后就被销毁的情景. 由于异步请求的结果处理是在Activity上进行的, 所以肯定会持有Activity的引用, 这就使得Activity无法被回收, 造成内存泄漏. 如果将异步请求交由ViewModel进行, 由于ViewModel的生命周期足够长, 这种情况就不会发生了.
**那么, ViewModel就绝对不会引起内存泄漏吗? 当然不是. **我们已经知道, ViewModel的生命周期一般是比Activity和Fragment要长的. 如果ViewModel持有了它们的引用, 自然就会引起内存泄漏. 当然, 一般情况下大家不会直接引用一个Activity或者Fragment. 但是我们要知道, 它们是实现了Context和LifecycleOwner的接口的, 这两个东西我们是会经常用到的, 所以很可能不经意间我们就引用了它们, 为系统带来了内存泄漏的风险.
当然, 谷歌也提供了一个折中的办法: 如果你一定要使用Context, 那么你可以扩展AndroidViewModel类. 这个类是ViewModel的一个扩展, 它的构造函数需要传入Application的引用. 我们知道, ApplicationContext是一个静态单例, 引用它并不会导致内存泄漏(当然, 如果不是必要的, 建议还是不要持有这个引用).
ViewModel的实现原理
ViewModel的存储
从获取ViewModel的那句代码里我们可以直到, 我们获取到的ViewModel是什么, 取决于我们传入的两个参数, 这种模式很像一个Key-Value对. 既然ViewModel是长期存在的, 那么一定有一个数据结构来存储所有ViewModel的子类实例, 我们猜测这个数据结构应该是一个Map.
在找到Map之前, 我们先来了解一下这个Key-Value对. 我们都知道Value是ViewModel的子类实例, 那么Key, 也就是我们传入的两个参数参数到底是什么呢?
1 | public ViewModelProvider( { ViewModelStoreOwner owner) |
这是对应的ViewModelProvider的构造函数的声明, 我们看到, 第一个参数, 也就是ViewModelProvider的构造函数的参数是一个ViewModelStoreOwner的接口, 显然AppCompatActivity和Fragment都实现了这个接口. 和LifecycleOwner一样, ViewModelStoreOwner实际上也只是标记了一个类是否含有ViewModelStore, 所以我们还要再看一眼ViewModelStore.
1 | public class ViewModelStore { |
这是ViewModelStore的源码. 不出我们所料, ViewModel确实是用一个Map维护着的. 但是如何确定Map的Key值呢? 那就还需要用到第二个参数, 也就get方法的参数. 再在ViewModelProvider中寻找, 我们很快就找到了这个方法:
1 | public <T extends ViewModel> T get( { Class<T> modelClass) |
上面两个get()方法是ViewModelProvider中的两个方法, 通常我们都使用第一个方法来获取ViewModel. 这个get方法通过反射获取传入参数的类名, 再从Owner的ViewModelStore中的Map去寻找对应的ViewModel. 如果Map不存在这个ViewModel, 那么就调用第二个get()方法, 先创建一个这样的ViewModel, 再将其放入Map中, 最后再返回这个ViewModel.
到此, ViewModel的存储原理就很清楚了:
作为ViewModelStoreOwner的Activity和Fragment拥有着一个ViewModelStore. 这个Store通过Map来存储所有和Activity或者Fragment绑定的ViewModel, Map的Key值由对应的ViewModel的类名再加工而来.
我们获取ViewModel时, 通过创建一个ViewModelProvider的实例来指定Store和Map的Key值, 通过Key值去对应的Map中获取到对应的ViewModel.
如果你还没有看明白, 那么我再打一个比方:
一个ViewModelStore好比数据库中的一个数据表.
数据表中的数据有两个字段, 一个是作为关键字的String类型字段, 另外一个字段就是ViewModel的实例. 关键字的String字段由ViewModel的类名加工而来.
一个ViewModelProvider实例好比一条SQL语句, 它指明了要在哪张数据表中获取哪条数据.
看到这儿, 我想大家应该对于ViewModel的存储有一个比较好的理解了.
ViewModel的生命周期实现
接下来我们来看一下ViewModel是怎么实现它的生命周期的.
ViewModel的实现和Lifecycle没有什么关系, 它的实现源自于静态内部类.
以AppCompatActivity为例. 我们先向上寻找AppCompatActivity实现ViewModelStoreOwner的父类——ComponentActivity(正是实现了LifecycleOwner的那个父类), 在它的源码中, 我们能找到下面的片段:
1 | static final class NonConfigurationInstances { |
在片段中可以看到, ComponentActivity声明了一个静态内部类: NonConfigurationInstances. 当ComponentActivity的getViewModelStore被调用时, ComponentActivity就认为它存储的ViewModel被改变了, 这时它就会将最新的ViewModelStore存储在NonConfigurationInstances中. 由于静态内部类与外部类是独立的, 即使ComponentActivity被重新创建了, 也不会影响NonConfigurationInstances的内容. ComponentActivity被重新创建后, 会通过onRetainNonConfigurationInstance()方法获取之前存储的ViewModelStore的最新副本, 这样就做到了即使重新创建Activity也不会影响ViewModel的效果. 当Activity的实例被销毁后, 因为再没有实例引用NonConfigurationInstances, 所以它会在一段时间后被回收掉, 这就实现了ViewModel生命周期略长于Activity的效果.