Android Jetpack(5):ViewModel
ViewModel介绍
ViewModel类是被设计用来以可感知生命周期的方式存储和管理 UI 相关数据,为了更好的以生命周期的方式管理界面相关的数据。ViewModel中数据会一直存活,即使configuration发生改变(比如旋转屏幕),数据仍然可以存在不会销毁。
Android中的ViewModel是一个可以用来存储UI相关的数据的类
那ViewModel为什么可以管理这些数据呢?
主要还是因为ViewModel的生命周期比Activtiy、Fragment生命周期来的更长。
ViewModel类允许数据在配置变化(例如屏幕旋转)后存活。
还记得MVP中的Model吗。这里的ViewModel有点类似MVP中的Model的作用。但是google出了一套AAC组件。这些组件让开发者能开发高效的项目。其中ViewModel也是其中组件之一。
AAC(Android Architecture Components) :实际上是android官方提供的一系列组件,用来实现MVVM架构的。
引入ViewModel的原因
-
Activity或Fragment这类应用组件都有自己的生命周期,他们的生命周期都是被Framework所管理。Framework可能会根据用户的一些操作以及设备的状态对Activity或Fragment进行销毁和重建。作为开发者,这些行为我们是无法干预的。伴随着Activity或Fragment的销毁和重建,它们当中的数据也会随着一起销毁和重建。对于一些简单的数据,Activity可以使用onSaveInstanceState()方法,并从onCreate的bundle中重新获取,但这一方法仅仅适合一些简单的UI状态,对于列表型这种庞大的数据类型并不适合。
-
Activity或Fragment经常会做一些异步的耗时操作,随之就需要管理这些异步操作得到的数据,并在destroyed的时候清理它们,从而避免内存溢出这类问题的发生。但是这样的处理会随着项目扩大而变得十分复杂。
-
Activity或Fragment本身需要处理很多用户的输入事件并和操作系统打交道,当它们还要花时间管理那些数据资源时,它们所在的类就会变得异常庞大,造就出所谓的god activities和god fragments,这样很尴尬。
所以引入ViewModel之后,数据就可以从UI中分离出来,让每个模块的职责更加清晰合理。并且当Activity或Fragment重建的时候,ViewModel会自动保留之前的数据并给新的Activity或Fragment使用。
ViewModel可以解决以下痛点
1. 数据持久化
在屏幕旋转的时候会经历 Activity 的销毁与重新创建,这里就涉及到数据保存的问题,显然重新请求或加载数据是不友好的。在 ViewModel 出现之前我们可以用 Activity 的 onSaveInstanceState() 机制保存和恢复数据,但缺点很明显,onSaveInstanceState只适合保存少量的可以被序列化、反序列化的数据,这种机制明显不合适。
ViewModel 生命周期图如下:
由图可知,ViewModel 生命周期是贯穿整个 activity 生命周期,包括 Activity 因旋转造成的重创建,直到 Activity 真正意义上销毁后才会结束。既然如此,用来存放数据再好不过了。
2. 异步回调问题
通常我们 App 需要频繁异步请求数据,比如调接口请求服务器数据。当然这些请求的回调都是相当耗时的,之前我们在 Activity 或 Fragment里接收这些回调。所以不得不考虑潜在的内存泄漏情况,比如 Activity 被销毁后接口请求才返回。处理这些问题,会给我们增添好多复杂的工作。但现在我们利用 ViewModel 处理数据回调,可以完美的解决此痛点。意思只要继承我们的ViewModel后,可能会出现的bug,google都帮我们处理了。
3. 分担 UI controller 负担
从最早的 MVC 到目前流行的 MVP、MVVM,目的无非是 明确职责,分离 UI Controller 负担。
UI Controller 比如 Activity 、Fragment 是设计用来渲染展示数据、响应用户行为、处理系统的某些交互。如果再要求他去负责加载网络或数据库数据,会让其显得臃肿和难以管理。
所以为了简洁、清爽、丝滑,我们可以分离出数据操作的职责给 ViewModel。
4. Fragments 间共享数据
比如在一个 Activity 里有多个 Fragment,这 Fragment 之间需要做某些交互。我之前的做法是接口回调,需要统一在 Activity 里管理,并且不可避免的 Fragment 之间还得互相持有对方的引用。
ViewModel的生命周期
ViewModel对象的范围由获取ViewModel时传递至ViewModelProvider的Lifecycle所决定。ViewModel始终处在内存中,直到Lifecycle永久地离开—对于Activity来说,是当它终止(finish)的时候,对于Fragment来说,是当它分离(detached)的时候。
val mainViewModel = ViewModelProvider(this).get(MainViewModel::class.java)
Activity在生命周期中可能会触发多次onCreate(),而ViewModel则只会在第一次onCreate()时创建,然后直到最后Activity销毁。
当ViewModel的实例生成之后,它会一直待在内存中,直到对应的Lifecycle彻底结束。下图展示了一个Activity经过旋转然后到结束运行这段期间各生命周期的状态,在Activity的生命周期旁边显示了其中ViewModel的生命周期。虽然这个图描述的是Activity的状态,但是Fragment和它是类似的。
从图中可以看出,在第一次调用Activity对象的onCreate()方法时创建了一个ViewModel。在Activity运行过程中可能会多次调用onCreate()方法(比如当设备屏幕旋转时),但是ViewModel一直存在,直到Activity结束并销毁。这意味着ViewModel不会因为它的创建者的一个配置变化而被销毁,Activity 的新实例将与现有的ViewModel重新连接。
这里最大的亮点是以生命周期的方式。举例:假如在Activity里使用。他会贯穿整个Activity里的生命周期。
ViewModel只会在Activity存活,且只会创建一次。当销毁时,会主动调用onClered。
因为在Activity存活时,只创建一次,那么在此Activity下的所有Fragment都可以共享一个ViewModel。
由于 ViewModel 生命周期可能长与 activity 生命周期,所以为了避免内存泄漏Google禁止在ViewModel中持有Context或activity或view的引用。如果非得使用Context,可以继承AndroidViewModel 类中获取ApplicationContext。
这张图也解释了为什么ViewModel中不能持有Activity、Fragment、view的引用。因为Activity在重建后是一个新的对象,如果ViewModel中持有旧对象的引用,这个旧对象可能就等不到释放,造成泄漏。
之前我们在activity销毁重建时,可以用activity的onSaveInstanceState()机制保存和恢复数据,但缺点明显,只适合保存少量的可以被序列化、反序列化的数据。假如我们需要保存一个比较大的数据,这个时候ViewModel就可以实现。
ViewModel使用
ViewModel使用流程
UI,也就是Activity或Fragment,它的职责仅仅是视图的管理(大部分是刷新工作),相当于是一个ViewController的角色。ViewModel类相当于数据集散地,UI要这个数据了,ViewModel就去帮它在仓库找好,无论是数据库还是网络都行。ViewModel拿到数据之后就通知UI,通常情况下这个通知由LiveData来完成。最后通过LiveData去找DataBinding,完成数据的刷新。
依赖
ViewModel 的依赖,可以查看官方文档,导入最新的版本。
java
def lifecycle_version = "2.2.0"
def arch_version = "2.1.0"
// ViewModel
implementation "androidx.lifecycle:lifecycle-viewmodel:$lifecycle_version"
// LiveData
implementation "androidx.lifecycle:lifecycle-livedata:$lifecycle_version"
kotlin
def lifecycle_version = "2.2.0"
def arch_version = "2.1.0"
// ViewModel
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
// LiveData
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
创建一个ViewModel类
继承ViewModel,需要注意的是ViewModel类中不应该持有Activity、Fragment、view的引用。
架构组件为UI控制器提供ViewModel助手类,ViewModel对象在配置更改期间会自动保留,以便它们保存的数据立即可用于下一个Activity或fragment实例。例如,如果您需要在应用中显示用户列表,请明确分配职责来获取数据并将用户列表保存到ViewModel,而不是Activity或fragment。
ViewModel一般配合 LiveData 使用。
class MainViewModel:ViewModel() {
private val mainLiveData=MutableLiveData<String>()
fun loadData() {
mainLiveData.postValue("xyh")
}
fun getMainLiveData(): MutableLiveData<String> {
return mainLiveData
}
}
然后你可以从一个Activity中加载数据,如下所示:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
//获取ViewModel
val mainViewModel = ViewModelProvider(this).get(MainViewModel::class.java)
//2.2.0-alpha02 以前的版本是通过下面的方式,新版本已弃用
//ViewModelProviders.of(this).get(MainViewModel::class.java)
//加载数据
mainViewModel.loadData()
mainViewModel.getMainLiveData().observe(this, object : Observer<String> {
override fun onChanged(t: String?) {
Log.e("xyh", "onChanged: $t")
}
})
}
}
如果Activity重新创建,它将接收由第一个Activity创建的相同的ViewModel实例。当持有ViewModel的Activity finish后,框架将调用ViewModel对象的onCleared()方法,以便它可以清理资源。
获取ViewModel得方式
val mainViewModel = ViewModelProvider(this).get(MainViewModel::class.java)
//2.2.0-alpha02 以前的版本是通过下面的方式,新版本已弃用
val mainViewModel= ViewModelProviders.of(this).get(MainViewModel::class.java);
this参数一般为Activity或Fragment,因此ViewModelProvider可以获取组件的生命周期。
构造器有参数的ViewModel
当我们的ViewModel需要进行构造器需要穿参数的时候,就不能像上面一样进行实例化了。而需要借助于ViewModelProvider的Fatory来进行构造。
class MainViewModel(val name: String) : ViewModel() {
private val mainLiveData = MutableLiveData<String>()
fun loadData() {
mainLiveData.postValue("xyh$name")
}
fun getMainLiveData(): MutableLiveData<String> {
return mainLiveData
}
class MainViewModelFactory(private val name: String) : ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
return MainViewModel(name) as T
}
}
}
可以看到在MainViewModel有一个内部类基础自ViewModelProvider.Factory接口。并重写了create方法。这个方法返回一个ViewModel这里我们就是要实例化MainViewModel对象,因此这里返回一个SharedViewModel对象,可以看到参数这时候就通过MainViewModelFactory传给了MainViewModel了。
这时候不光是MainViewModel要进行修改,在进行MainViewModel的获取的时候也是需要进行相应的修改的:
val mainViewModel = ViewModelProvider(
this,
MainViewModel.MainViewModelFactory("赵丽颖")
).get(MainViewModel::class.java)
其实就是在获取ViewModelProvider对象的of方法有修改。这里用的是带有Factory的of方法。传入刚才在MainViewModel中写的Factory的实例。
AndroidViewModel
使用ViewModel的时候,需要注意的是ViewModel不能够持有View、Lifecycle、Acitivity引用,而且不能够包含任何包含前面内容的类。因为这样很有可能会造成内存泄漏。
那如果需要使用Context对象改怎么办。这时候我们可以给ViewModel一个Application。Application是一个Context,而且一个应用也只会有Application。
我们自己添加Application?其实没必要Google还有一个AndroidViewModel。这是一个包含Application的ViewModel。
AndroidViewModel 是ViewModel的一个子类,可以直接调用getApplication(),由此来访问应用的全局资源。
下面是一个AndroidViewModel的源码:
public class AndroidViewModel extends ViewModel {
@SuppressLint("StaticFieldLeak")
private Application mApplication;
public AndroidViewModel(@NonNull Application application) {
mApplication = application;
}
/**
* Return the application.
*/
@SuppressWarnings({"TypeParameterUnusedInFormals", "unchecked"})
@NonNull
public <T extends Application> T getApplication() {
return (T) mApplication;
}
}
因为ViewModel在指定的Activity或Fragment实例外存活,它应该永远不能引用一个View,或持有任何包含Activity context引用的类。如果ViewModel需要Application的context(如获取系统服务),可以扩展AndroidViewmodel,并拥有一个构造器接收Application。
使用ViewModel在Fragment间共享数据
一个Activity中的多个Fragment相互通讯是很常见的。之前每个Fragment需要定义接口描述,所属Activity将二者捆绑在一起。此外,每个Fragment必须处理其他Fragment未创建或不可见的情况。通过使用ViewModel可以解决这个痛点,这些Fragment可以使用它们的Activity共享ViewModel来处理通讯。
public class SharedViewModel extends ViewModel {
private final MutableLiveData<Item> selected = new MutableLiveData<Item>();
public void select(Item item) {
selected.setValue(item);
}
public LiveData<Item> getSelected() {
return selected;
}
}
public class MasterFragment extends Fragment {
private SharedViewModel model;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
itemSelector.setOnClickListener(item -> {
model.select(item);
});
}
}
public class DetailFragment extends Fragment {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
SharedViewModel model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
model.getSelected().observe(this, item -> {
// Update the UI.
});
}
}
注意:上面两个Fragment都用到了如下代码来获取ViewModel,getActivity()返回的是同一个宿主Activity,因此两个Fragment之间返回的是同一个SharedViewModel对象。
SharedViewModel model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
这种方式的好处包括:
- Activity不需要做任何事情,也不需要知道通讯的事情
- Fragment不需要知道彼此,除了SharedViewModel进行联系。如果它们(Fragment)其中一个消失了,其余的仍然能够像往常一样工作。
- 每个Fragment有自己的生命周期,而且不会受其它Fragment生命周期的影响。事实上,一个Fragment替换另一个Fragment,UI的工作也不会受到任何影响。
总结
-
ViewModel职责是为Activity或Fragment管理、请求数据,具体数据请求逻辑不应该写在ViewModel中,否则ViewModel的职责会变得太重,此处需要一个引入一个Repository,负责数据请求相关工作。具体请参考 Android架构组件。
-
ViewModel可以用于Activity内不同Fragment的交互,也可以用作Fragment之间一种解耦方式。
-
ViewModel也可以负责处理部分Activity/Fragment与应用其他模块的交互。
-
ViewModel生命周期(以Activity为例)起始于Activity第一次onCreate(),结束于Activity最终finish时。
ViewModel的内部实现
首先是通过ViewModelProvider(this)构造方法,创建一个ViewModelProvider,并在其构造方法中,获取到存储ViewModel的ViewModelStore对象。
public ViewModelProvider(@NonNull ViewModelStoreOwner owner) {
this(owner.getViewModelStore(), owner instanceof HasDefaultViewModelProviderFactory
? ((HasDefaultViewModelProviderFactory) owner).getDefaultViewModelProviderFactory()
: NewInstanceFactory.getInstance());
}
public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) {
mFactory = factory;
mViewModelStore = store;
}
owner就是我们传入的this(FragmentActivity、AppCompatActivity、Fragment)。
我们看看getViewModelStore方法:
当配置发生更改时一般会造成数据丢失 而NonConfigurationInstances实例则可以在配置发生变化时保存一些数据和状态,在oncreate方法恢复使数据不会丢失。
当配置发生变化的时候它保存了ViewModelStore。所以这里先从NonConfigurationInstance实例中获取ViewModelStore
@NonNull
@Override
public ViewModelStore getViewModelStore() {
if (getApplication() == null) {
throw new IllegalStateException("Your activity is not yet attached to the "
+ "Application instance. You can't request ViewModel before onCreate call.");
}
if (mViewModelStore == null) {
//如果当前的mViewModelStore为空,会先向nc中取mViewModelStore,这里边存储的就是上一次Activity对应的实例的mViewModelStore
NonConfigurationInstances nc =
(NonConfigurationInstances) getLastNonConfigurationInstance();
if (nc != null) {
// Restore the ViewModelStore from NonConfigurationInstances
mViewModelStore = nc.viewModelStore;
}
if (mViewModelStore == null) {
mViewModelStore = new ViewModelStore();
}
}
return mViewModelStore;
}
可见,这里的关键是NonConfigurationInstances。在设备旋转的时候,当前Activity被销毁了,mViewModelStore等数据会被封装到NonConfigurationInstances中存储出来,创建了新的对象会重新传入该NonConfigurationInstances。
ViewModelStore :
用于缓存ViewModel的一个操作类。
public class ViewModelStore {
//用于存储ViewModel的集合
private final HashMap<String, ViewModel> mMap = new HashMap<>();
final void put(String key, ViewModel viewModel) {
ViewModel oldViewModel = mMap.put(key, viewModel);
if (oldViewModel != null) {
oldViewModel.onCleared();
}
}
final ViewModel get(String key) {
return mMap.get(key);
}
Set<String> keys() {
return new HashSet<>(mMap.keySet());
}
/**
* Clears internal storage and notifies ViewModels that they are no longer used.
*/
public final void clear() {
for (ViewModel vm : mMap.values()) {
vm.clear();
}
mMap.clear();
}
}
可见ViewModelStore还是比较简单的,就是用HashMap来存储的数据。
ViewModel的生成和获取相关, 是在ViewModelProvider的get方法中:
@NonNull
@MainThread
public <T extends ViewModel> T get(@NonNull Class<T> modelClass) {
//获取我们传入的NumberViewModel简称(就这样叫吧),作为key的一部分
String canonicalName = modelClass.getCanonicalName();
//判断是不是局部类与匿名类
if (canonicalName == null) {
throw new IllegalArgumentException("Local and anonymous classes can not be ViewModels");
}
return get(DEFAULT_KEY + ":" + canonicalName, modelClass);
}
//获取ViewModel实例
public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
//从集合中获取ViewModel并检测是不是已经实例化,如果存在就不要新建
ViewModel viewModel = mViewModelStore.get(key);
//判断viewModel是否是modelClass类的实例
if (modelClass.isInstance(viewModel)) {
if (mFactory instanceof OnRequeryFactory) {
((OnRequeryFactory) mFactory).onRequery(viewModel);
}
//如果是则直接返回已存在的viewModel
return (T) viewModel;
} else {
//noinspection StatementWithEmptyBody
if (viewModel != null) {
// TODO: log a warning.
}
}
//否则通过工厂来新生成一个viewModel实例,用反射获取到ViewModel的实例
if (mFactory instanceof KeyedFactory) {
viewModel = ((KeyedFactory) (mFactory)).create(key, modelClass);
} else {
viewModel = (mFactory).create(modelClass);
}
//把新生成的viewModel实例存入mViewModelStore中
mViewModelStore.put(key, viewModel);
return (T) viewModel;
}
其实主要还是利用了反射得到了ViewModel的实例。我们才可以使用ViewModel里面的方法。
那么什么是反射呢? 这里简单介绍一下:
反射机制:反射机制允许程序在执行期借助于Reflection API取得任何类的内部信息,并能直接操作任意对象的内部属性及方法
优点:可以实现动态创建对象和编译,体现出很大的灵活性
缺点:对性能有影响,此类操作总是慢于直接执行相同的操作
最后说一下VierModel是如何销毁的
/**
* 在FragmentActivity的onDestroy方法中调用了mViewModelStore.clear()
*/
@Override
protected void onDestroy() {
super.onDestroy();
if (mViewModelStore != null && !isChangingConfigurations()) {
mViewModelStore.clear();
}
mFragments.dispatchDestroy();
}
/**
* 先遍历ViewModel实例 调用各自的clear()
* 再清除集合中的ViewModel实例
*/
public final void clear() {
for (ViewModel vm : mMap.values()) {
vm.clear();
}
mMap.clear();
}
/**
* 清除一些标记 并调用onCleared()
*/
@MainThread
final void clear() {
mCleared = true;
// Since clear() is final, this method is still called on mock objects
// and in those cases, mBagOfTags is null. It'll always be empty though
// because setTagIfAbsent and getTag are not final so we can skip
// clearing it
if (mBagOfTags != null) {
synchronized (mBagOfTags) {
for (Object value : mBagOfTags.values()) {
// see comment for the similar call in setTagIfAbsent
closeWithRuntimeException(value);
}
}
}
onCleared();
}
//通过继承ViewModel 可以重写该方法。 该方法会在ViewMode被销毁前调用
protected void onCleared() {
}
通过源码分析ViewModel有三个重要的类:ViewModel 、ViewModelProvider 、 ViewModelStore
ViewModel :负责准备和管理数据的类,该抽象类其实是声明一些通用方法
ViewModelProvider :ViewModel 的核心类,主要是利用反射实例化出ViewModel 对象。利用工厂模式生产出具体的ViewModel 实例。
ViewModelStore:缓存ViewModel实例的一些操作(存储、获取、清除)
核心原理简单通俗描述如下:
- ViewModel类存储了Actvity的UI数据
- ViewModelStore又存储了ViewModel实例
- 在配置发生变化的时候在FragmentActivity.onRetainNonConfigurationInstance()方法中利用NonConfigurationInstances保存了ViewModelStore实例
- 并在FragmentActivtiy.oncreate()方法中恢复了ViewModelStore。也就是保存了ViewModel。所以数据才不会在配置更改时丢失
- 最后在FragmentActivtiy.onDestroy()方法中清除存储在ViewModelStore中的ViewModel对象。
上一篇: Android 文件管理器的列表界面
下一篇: HIve面试题