ViewModel类设计用于,以存在生命周期的方式来存储和管理UI相关的数据。ViewModel允许数据保存于配置发生变化的情况,例如屏幕旋转。ViewModel优点在于:共享数据、保存临时数据、视图解耦、利于UI更新。
Android框架管理UI控制器的生命周期,例如activities、fragments。框架可能会销毁或者重新创建UI控制器,为了响应用户动作事件,或者设备事件已经不受控制了。如果系统销毁或者重建,所有与UI相关的临时数据将会丢失。对于简单少量数据,可以调用onSaveInstanceState方法来存储,然后从onCreate方法读取bundle来恢复数据。但是,这个方法仅适合可序列化的少量数据,并不适合大量数据,比如用户列表或者bitmap位图。
另外一个问题是,UI控制器经常发起异步调用,这样会带来一定耗时。UI控制器需要管理这些调用,并且确保在它们销毁后清理系统,从而避免内存泄漏。同时,UI控制器需要负责从网络或者数据库加载数据。总而言之,UI控制器工作很多,处理逻辑也变得复杂起来。
有更简单更高效的方法,从UI控制器逻辑分离出视图数据关系。那就是使用ViewModel,导入依赖:
implementation 'android.arch.lifecycle:extensions:1.1.1'
结构组件为UI控制器提供ViewModel帮助类,负责为UI准备数据。ViewModel在配置发生变化时会自动保存,所以它们携带的数据可以立刻用于下一个activity或者fragment。如果我们需要在应用程序显示用户列表,确保ViewModel可以获取并持有用户列表。我们可以自定义ViewModel:
public class MyViewModel extends ViewModel {
private MutableLiveData> users;
public LiveData> getUsers() {
if (users == null) {
users = new MutableLiveData>();
loadUsers();
}
return users;
}
private void loadUsers() {
//异步操作来获取数据
}
}
我们可以这样从activity中访问list列表:
public class MyActivity extends AppCompatActivity {
public void onCreate(Bundle savedInstanceState) {
MyViewModel model = ViewModelProviders.of(this).get(MyViewModel.class);
model.getUsers().observe(this, users -> {
// 更新 UI
});
}
}
一、ViewModel的生命周期
当获取ViewModel实例时,ViewModel对象的生命周期传递给ViewModelProvider。ViewModel一直保留于内存中,直到生命周期作用域永久消失。下图阐述一个activity经历旋转和销毁后,各种生命周期状态变化,同时也展示了关联activity的ViewModel生命周期:
二、fragment之间共享数据
activity的两个甚至多个fragment之间相互通信,这是很常见的事情。可以想象,master-detail fragment的案例,master fragment负责展示列表,detail fragment负责展示选中item的详情。因为两个fragment之间需要数据交互,所以需要定义一些接口,处理逻辑变得复杂起来。
这个通用痛点可以使用ViewModel对象来解决。这些fragments可以共享一个ViewModel,使用activity来处理它们之间的通信,如下面代码所示:
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 ->
// 更新UI
});
}
}
需要注意的是,所有fragments都需要检查activity是否包含它们。那样,当某个fragment获取ViewModelProvider,它们会收到作用于当前activity的ShareViewModel实例。这个方法提供如下方便之处:
1、activity不需要做任何事情;
2、fragments不需要了解ShareViewModel之间的联系;
3、每个fragment有自己的生命周期,不影响其他fragment;
三、使用ViewModel替换加载器
例如CursorLoader之类的加载器,经常用于应用程序UI在数据库异步查询数据。我们可以使用ViewModel,更少的类,来替换加载器。ViewModel可以把UI控制器从数据加载操作中分离出来,这意味着我们在类与类之间使用更少的强引用。下图展示的是普通加载器加载过程:
ViewModel与Room、LiveData协同工作,可以替换Loader。当数据库数据发生变化时,Room会通知LiveData,接着,LiveData会更新UI数据,工作过程如下图所示: