系列文章导航:
- 【译】Google官方推出的Android架构组件系列文章(一)App架构指南
- 【译】Google官方推出的Android架构组件系列文章(二)将Architecture Components引入工程
- 【译】Google官方推出的Android架构组件系列文章(三)处理生命周期
- 【译】Google官方推出的Android架构组件系列文章(四)LiveData
- 【译】Google官方推出的Android架构组件系列文章(五)ViewModel
- 【译】Google官方推出的Android架构组件系列文章(六)Room持久化库
原文地址:https://developer.android.com/topic/libraries/architecture/viewmodel.html
ViewModel
类被设计用来存储和管理UI相关数据,以便数据能在配置更改(比如屏幕旋转)中生存下来。
应用组件,比如activity和fragment,具有由Android框架管理的生命周期。框架可以根据用户行为或完全不由你控制的设备事件来决定销毁还是重建他们。
因为这些对象可能会被操作系统销毁或重建,任何你持有的这些组件中的数据都会丢失。比如,如果在activity中有一个用户列表,当由于配置更改而引起的activity重建时,新的activity不得不再次拉取用户列表。对于简单的数据,activity可以使用onSaveInstanceState()
方法,然后从onCreate()
的bundle
中恢复数据,但这种方法仅仅适用于少量数据,比如UI状态,对于潜在的大量数据,比如用户列表,则不适用。
另外一个问题是,这些UI控制器(activity
,fragment
等等)经常需要做一些需要花费一定时间才能返回的异步调用。他们需要管理这些调用,当其销毁时清理它们,防止内存泄漏。这需要大量维护工作,并且当对象由于配置更改重建时,这是浪费资源的,因为它需要发出相同的调用。
最后但并非不重要的是,这些UI控制器已经需要响应用户操作或者处理操作系统通信。当他们还需要手动处理其资源时,将使得类膨胀,创造“上帝activity”(或“上帝fragment”),也就是说,采用一个单独的类来试图自己处理所有的应用程序工作,而不是将工作委托给其他类。这也使得测试变得更改困难。
将视图数据所有权从UI控制器逻辑分离是更容易和更高效的。Lifecycle
提供了一个名为ViewModel
的新类,一个负责为UI准备数据的辅助类。在配置改变时,ViewModel
会自动保留,这样一来,它持有的数据能够立即对下一个activity
或fragment
实例可用。在我们上面提到的例子里,获取以及保存用户列表数据的责任应该是ViewModel
的,而不是activity
或fragment
。
public class MyViewModel extends ViewModel {
private MutableLiveData<List<User>> users;
public LiveData<List<User>> getUsers() {
if (users == null) {
users = new MutableLiveData<List<Users>>();
loadUsers();
}
return users;
}
private void loadUsers() {
// do async operation to fetch users
}
}
现在activity可以像下面这样访问用户列表:
public class MyActivity extends AppCompatActivity {
public void onCreate(Bundle savedInstanceState) {
MyViewModel model = ViewModelProviders.of(this).get(MyViewModel.class);
model.getUsers().observe(this, users -> {
// update UI
});
}
}
如果activity重建,它将接收到由上个activity创建的同一个MyViewModel
实例。当ViewModel
的拥有者activity
结束时,框架调用ViewModel
的onCleared()
方法,以便它可以清理资源。
注意 :因为
ViewModel
的生命周期超出了具体的activity和fragment实例,所以它不应该引用任何View或任何可能持有activity context引用的类。
Fragment间共享数据
一个activity中的两个或多个activity需要互相通信的情况是很常见的。这并非微不足道,因为两个fragment需要定义一些接口描述,并且其所有者activity必须将两者绑定到一起哦。此外,两个fragment必须处理其他fragment还没创建或不可见的情况。
通过使用ViewModel
对象可以解决这个常见的痛点。假设一个常见的主-详细fragment场景:一个包含用户可以选择项的列表fragment,另一个fragment用来展示选择项的内容。
这些fragment共享一个ViewModel
,使用他们的activity范围来处理这种通信。
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 LifecycleFragment {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
SharedViewModel model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
model.getSelected().observe(this, { item ->
// update UI
});
}
}
请注意,两个fragment
在获取ViewModelProvider
时都调用了getActivity()
。这意味着,他们两个将会收到同一个SharedViewModel
实例,这个实例的作用域是其activity。
这种方式的好处包括:
- activity不需要做和知道任何关于这次通信的事情。
- fragment不需要互相知道除了
SharedViewModel
约束。如果其中一个消失,另一个都能正常工作。 - 每个fragment有自己的生命周期,不受其他fragment生命周期的影响。实际上,
在一个fragment替换另一个fragment的UI中,UI可以正常工作,没有任何问题。
ViewModel的生命周期
在获取ViewModel
时,ViewModel对象的作用域绑定到传递给ViewModelProvider
的Lifecycle
。ViewModel
保留在内存中,直到其绑定的Lifecycle
永久消失——如果是activity
,则是其finish
时,如果是fragment
,则是其detached
时。
ViewModel vs SavedInstanceState
ViewModel提供了一种方便的方法在配置更改之间保留数据,但是如果应用程序被操作系统杀死,则他们不会保留。
比如,如果用户离开应用,然后几个小时后回来,进程在那个时候已经被杀掉,Android系统将从保存的状态里面还原Activity
。所有框架组件(view,acitivty,fragment)使用保存实例状态机制来保存他们的状态,因此大部分时间,你啥也不用做。你可以使用onSaveInstanceState
回调将自定义数据加入到bundle
中。
通过onSaveInstanceState
保存的数据是保留在系统进程内存中,Android系统允许你仅仅保留一个小块数据,因此这不是保留你的应用实际数据的好地方。你应该谨慎使用它来存放哪些不容易被UI组件表示的东西。
举个例子,如果有一个展示国家信息的用户界面,你永远也不要把Country
对象放到保存实例状态中。你可以把countryId
放到里面(除非它已经由View或Fragment参数保存)。实际的对象应该保存在数据库中,ViewModel
可以通过保存的countryId
获取。