深入理解 ViewModel
本文主要研究了ViewModel
的概念、使用方式和内部实现原理。
1. ViewModel概念
ViewModel
用来管理和存储UI相关的数据,和普通的 data store 不同的是:ViewModel
会帮你处理 UI Controller 的生命周期带来的问题。
这里需要解释,为什么我们需要处理 UI Controller的生命周期,不处理会有什么问题呢?
-
数据需要在 UI Controller destroyed 后清理,防止内存泄露。
-
数据需要处理 UI Controller 的生命周期。举个例子,Activity 在 configuration changes 后(旋转屏幕等),系统会销毁之前的Activity,并重建一个Activity 实例。这时数据如何在这两个Activity实例之间保持一致。
没有ViewModel
,解决问题的思路:
- 使用 static data holder(需要处理lifecycle)
- 将数据持久化
- 持久化的位置——本地、云端?
- 持久化时机——即时、lifecycle callbacks?
ViewModel
号称解决了上述问题,它是怎么做到的呢?
2. ViewModel使用
下面先提供个demo:
1 . MyViewModel.java
使用 ViewModel 很简单,直接继承 ViewModel
就好了:
public class MyViewModel extends ViewModel { |
2 . MainActivity.java
和 UI Controller 绑定:
|
一般这样就足够了,不过,工厂方法ViewModelProviders
默认使用 ViewModel 的无参构造函数。如果需要指定构造函数,还需要自己实现Factory
接口的create
方法。
3 . MyViewModelFactory.java
public class MyViewModelFactory implements ViewModelProvider.Factory { |
MyViewModel.java
public class MyViewModel extends ViewModel { |
使用姿势变成:
@Override |
另外,还可以通过传入同一个getActivity()
,使得ViewModel成为Activity的多个Fragment之间数据传递层。
需要注意的是,不要在ViewModel中持有 UI Controller 或者 Context,这是因为ViewModel的寿命长于 UI Controller 从而会导致内存泄露。
3. ViewModel优点
- ViewModel 数据层 和 UI Controller 之间分离的很干净。UI Controller 不用负责获取数据,也不用在重建时负责数据的有效性。
- ViewModel 数据层能感知到 UI Controller 的生命周期:保证 UI Controller 重建后,持有的是同一个ViewModel数据实例; UI Controller 结束生命周期后,系统自动调用ViewModel的
clear()
,释放资源。 - 配合 LiveData 使用效果更佳。
- 之前放到
onSaveInstanceState()
的复杂数据,现在可以放到ViewModel(系统UI相关的除外) - 由于职责划分更加清晰,测试更方便。
4. drive deeper
ViewModel 是如何实现在 UI Controller 重建后维持同一个实例的呢?
回答这个问题之前,先补充一点背景知识。
Fragment.java
里有一个方法:
public void setRetainInstance(boolean retain) { |
这个方法的注释如下:
Control whether a fragment instance is retained across Activity re-creation (such as from a configuration change). This can only be used with fragments not in the back stack. If set, the fragment lifecycle will be slightly different when an activity is recreated:
onDestroy()
will not be called (butonDetach()
still will be, because the fragment is being detached from its current activity).onCreate(Bundle)
will not be called since the fragment is not being re-created.onAttach(Activity)
andonActivityCreated(Bundle)
will still be called.
上面这段话体现在Fragment生命周期上如图所示:
注意,在官方图的基础上加的红线部分就是设置了setRetainInstance
之后的生命周期变动。
Fragment在设置setRetainInstance(true)
后,当 host Activity re-creation 时,fragment不会被destroyed,而是keep在内存中。当re-creation时,fragment 跳过 onDestroy()
和 onCreate()
生命周期,并重新执行一遍 onAttach()
和 onDetach()
之间的回调。
因此在 Activity re-creation 后,fragment还是原来的那个实例。
看完这个介绍,是不是有所启发了?
回到 ViewModel 的实现来。
下面大图是 Google Android Architecture Components 的部分框架(图来自Joe Birch),
红色框线部分是ViewModel
的部分。
可见ViewModel主要构成部分如下:
ViewModelProviders
ViewModelProvider
ViewModelStores
ViewModelStore
ViewModel
其中,主角 ViewModel
是一个只有onCleared
方法的抽象类
public abstract class ViewModel { |
上述问题的答案显然不在这里。
下面从调用入口开始看:
MyViewModel viewModel = ViewModelProviders.of(this, factory).get(MyViewModel.class); |
获取ViewModel
两个步骤:
- 通过
ViewModelProviders.of()
返回的ViewModelProvider
- 再通过
ViewModelProvider.get()
获取ViewModel实例。
ViewModelProviders
是创建ViewModelProvider
的工具类,通过of()
方法获取ViewModelProvider
贴of
代码(其一):
|
上述代码简单的调用构造函数创建了ViewModelProvider
。
而ViewModelProvider
又是创建ViewModel
的工具类,提供了get
方法获得 ViewModel
。
|
由上面代码可以看到,构造函数传入的ViewModelStore
(ViewModelStores.of()
的返回值) 这里充当了ViewModel缓存的功能,Factory
则直接负责ViewModel的创建。
ViewModelStores
和ViewModelStore
的关系也是类似的,ViewModelStores
是创建ViewModelStore
的工具类。
ViewModelStores
也有几个of
方法。
public static ViewModelStore of(FragmentActivity activity) { |
在上述代码的具体实现中,注意holderFragmentFor
这里,大概猜到了具体实现和Fragment
有关。
继续深入。
holderFragmentFor
是HolderFragment
的类方法,参数为ViewModelProviders.of()
传入的activity
实例(或者fragment
),返回一个HolderFragment
实例。内部实现则是委托给了HolderFragmentManager
。
(RestrictTo.Scope.LIBRARY_GROUP) |
ViewModelStores.of()
调用HolderFragment
的getViewModelStore()
,取出其内部持有ViewModelStore
,并作为返回值返回。
HolderFragment.java
其他的代码不贴,只看构造函数就行。
public HolderFragment() { |
setRetainInstance(true)
保证了HolderFragment
在Activity重建时不会被销毁,在这个基础上,HolderFragment
保证了ViewModelStore
在 Activity 重建之后维持同一个实例。
至于ViewModelStore
的实现,很简单,不到50行代码:
public class ViewModelStore { |
取得ViewModelStore
之后,通过 get(key)
最终获得了一个ViewModel
。
那么,HolderFragmentManager
又是怎么将 UI Controller 和 HoldFragment
关联起来的呢?
第一次获取ViewModel
时,HolderFragmentManager
创建HolderFragment
,并将其添加到 activity state。
private static HolderFragment createHolderFragment(FragmentManager fragmentManager) { |
当 Activity 重建后,第二次获取ViewModel时,HolderFragmentManager
内部通过调用FragmentManager.findFragmentByTag
来查找之前commit的HolderFragment
,由于HolderFragment
不会在Activity重建时销毁,所以这里返回的是同一个HolderFragment
实例。
private static HolderFragment findHolderFragment(FragmentManager manager) { |
总结HolderFragmentManager
的存储关系如下图:
一个 UI Controller 对应一个HolderFragment
。两者之间是通过FragmentManager.findFragmentByTag
互相关联起来的。
HolderFragment
持有ViewModelStore
实例。而 ViewModelStore
内部通过String key对应着多个ViewModel
。
其实很早很早以前 Android 社区就流行使用没有UI的 Fragment
来做为 MVC 中的 Controller。这种Fragment
被称为Headless Fragment
,其中就利用到了 setRetainInstance(true)
的这个特性。
ViewModel
的设计实现,也算是官方承认了Headless Fragments
的这种非正统使用模式了。(Fragment
的设计者估计做梦也不会想到竟然还有这种操作吧)。
Author: deskid
Link: https://deskid.github.io/2017/07/28/ViewModel/
License: 知识共享署名-非商业性使用 4.0 国际许可协议