ViewModel Overview
ViewModel Overview
ViewModel旨在以生命周期关联的方式存储和管理与UI相关的数据。ViewModel允许数据在配置更改(例如屏幕旋转)后继续存在。
引入方式
dependencies {
def lifecycle_version = "2.0.0"
// ViewModel and LiveData
implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version"
// alternatively - just ViewModel
implementation "androidx.lifecycle:lifecycle-viewmodel:$lifecycle_version" // For Kotlin use lifecycle-viewmodel-ktx
// alternatively - just LiveData
implementation "androidx.lifecycle:lifecycle-livedata:$lifecycle_version"
// alternatively - Lifecycles only (no ViewModel or LiveData). Some UI
// AndroidX libraries use this lightweight import for Lifecycle
implementation "androidx.lifecycle:lifecycle-runtime:$lifecycle_version"
annotationProcessor "androidx.lifecycle:lifecycle-compiler:$lifecycle_version" // For Kotlin use kapt instead of annotationProcessor
// alternately - if using Java8, use the following instead of lifecycle-compiler
implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version"
// optional - ReactiveStreams support for LiveData
implementation "androidx.lifecycle:lifecycle-reactivestreams:$lifecycle_version" // For Kotlin use lifecycle-reactivestreams-ktx
// optional - Test helpers for LiveData
testImplementation "androidx.arch.core:core-testing:$lifecycle_version"
}
UI controller的生命周期由Android framework管理,framework可以决定销毁或重建UI controller来响应一些用户的操作或是完全不受用户控制的设备事件。
在系统销毁或重建UI controller时,所有存在里面的和UI相关的临时数据都会丢失。例如:一个Activity中有个用户列表,当Activity由于配置变化而recreate时,新的Activity必须重新取一次用户列表。对于简单的数据,Activity可以使用onSavedInstanceState()方法来restore数据。但这个方法只适用于可以序列号后再反序列号的少量数据,不适用于大量数据,如用户列表或者Bitmap。
另一个问题就是UI controller需要经常进行异步调用来执行一些耗时操作,所以UI controller也得管理这些调用,并确保他们销毁后系统能正确清理掉,以避免潜在的内存泄露。
像Activity、Fragment这类UI controller主要用于展示数据,对用户操作做出反应,或处理系统级的对话,如permission request。如果让UI controller再负责从数据库或网络加载数据的话,会太冗余膨胀,会让单个类处理了APP的所有工作,而不是把工作委托给其他类,同时也增加测试难度
所以需要将view data的管理从UI controller分离出来。
实现ViewModel
Android 架构组件为UI controller提供了ViewModel类,负责为UI准备数据。ViewModel对象在配置变化期间会自动保留,以便下个Activity或Fragment实例能立即获取到数据。例如,你需要在APP中展示用户列表,把获取和保存用户列表放在ViewModel来负责,而不是Activity或Fragment。
public class MyViewModel extends ViewModel {
private MutableLiveData> users;
public LiveData> getUsers() {
if (users == null) {
users = new MutableLiveData>();
loadUsers();
}
return users;
}
private void loadUsers() {
// Do an asynchronous operation to fetch users.
}
}
随后可以在Activity中读取用户信息
public class MyActivity extends AppCompatActivity {
public void onCreate(Bundle savedInstanceState) {
// Create a ViewModel the first time the system calls an activity's onCreate() method.
// Re-created activities receive the same MyViewModel instance created by the first activity.
MyViewModel model = ViewModelProviders.of(this).get(MyViewModel.class);
model.getUsers().observe(this, users -> {
// update UI
});
}
}
当Activity重新创建时,会读取到Activity第一次创建时创建的MyViewModel实例。当持有ViewModel的Activity关闭时,framework调用ViewModel对象的onCleared()方法来清理资源
ViewModel不可以引用View、Lifecycle或任何可能包含activity context的类
ViewModel的设计就是为了存活在view或LifecycleOwner的生命周期之外。这样也方便测试覆盖ViewModel,因为ViewModel不了解view或 Lifecycle对象。ViewModel可以包含LifecycleOwner,例如LiveData对象。但ViewModel绝不能观察到LiveData的变化(LiveData这类属于有生命周期感知的可观察对象)。如果ViewModel需要Application context,例如要获取system service,那么可以继承AndroidViewModel类,这个类有个接收Application参数的构造函数。
ViewModel的生命周期
当获取ViewModel时,ViewModel对象被限定在为传递给ViewModelProvider的Lifecycle。ViewModel会一直在内存中,直到它被限定的Lifecycle永久的销毁掉:如Activity中的finish,Fragment中的detach。
图中展示了Activity在经历了旋转然后finish的生命周期状态。旁边是关联了Activity生命周期的ViewModel的生命周期。对于Fragment也同理。

我们一般在系统第一次调用Activity的onCreate()方法时请求一个ViewModel对象。但Activity的生命周期中系统可能会多次调用onCreate()方法,例如屏幕旋转的时候。而最初请求到的这个ViewModel会一直存在直到Activity的finish和destroy。
Fragment之间共享数据
一个很常见的需求就是一个Activity中的多个Fragment需要进行通信。master-detail fragments就是个常见情况:用户在一个Fragment中选择一项,在另一个Fragment中要展示这一项的详情。很常见的方法就是两个Fragment都要定义一堆接口,而且host Activity必须把他们绑定在一起。此外两个Fragment都要处理其他Fragment未创建或不可见的情况。
使用ViewModel可以解决这个痛点。Fragment可以共享他们host Activity的ViewModel来通信。
public class SharedViewModel extends ViewModel {
private final MutableLiveData- selected = new MutableLiveData
- ();
public void select(Item item) {
selected.setValue(item);
}
public LiveData
- 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都是使用包含他们的Activity来获取到ViewModelProvider返回的同一个SharedViewModel实例,这个实例受控于他们的Activity。
有以下好处:
- Activity完全不需要做任何事,也不知道Fragment之间的通信
- 除了和SharedViewModel的连接,Fragment也不需要知道其他Fragment。如果一个Fragment消失,另一个依旧正常使用。
- 每个Fragment拥有自己的生命周期,不受其他Fragment影响,如果一个Fragment替代另一个,UI还能正常工作。
使用ViewModel来替换Loader
CursorLoader等Loader class经常用于保持APP中UI的数据和数据库同步。现在我们可以使用ViewModel和一些其他类来取代loader,使用ViewModel将UI controller和data loading的操作分开,这样就减少了class之间的依赖。
使用loader的常见方法是:APP中使用CursorLoader来观察数据库的内容,当数据库的值发生改变时,loader自动触发重新加载数据并更新UI

ViewModel配合Room和LiveData来取代loader。ViewModel确保数据不受配置变化影响而被销毁,Room在数据库变化时通知LiveData,而LiveData返回来用新的数据更新UI。

随着数据变得越来越复杂,我们可能选择单独一个类来专门加载数据。ViewModel的作用是为UI controller封装数据,来使数据能够不因为配置变化而销毁。关于如何在配置变化时加载、持久化数据,可查看Saving UI States
Samples
- Android Architecture Components basic sample
- Sunflower, a gardening app illustrating Android development best practices with Android Jetpack.
Codelabs
- Android Room with a View (Java) (Kotlin)
- Android lifecycle-aware components codelab