ViewModel 负责根据生命周期来存储和管理 UI 相关(Activity 或 Fragment)的数据。当配置发生变化时,如:屏幕方向旋转,它可以保证数据不被销毁。
你还在为开发中频繁切换环境打包而烦恼吗?快来试试 Environment Switcher 吧!使用它可以在app运行时一键切换环境,而且还支持其他贴心小功能,有了它妈妈再也不用担心频繁环境切换了。https://github.com/CodeXiaoMai/EnvironmentSwitcher
为什么要用 ViewModel
Android Framework 层管理 UI 控制器(Activity/Fragment)的生命周期,它可以决定销毁或者重新创建一个 UI 控制器,以响应某些用户操作或完全无法控制的设备事件。如果系统销毁或重新创建 UI 控制器,那么存储在其中的任何与 UI 相关的临时数据都将丢失。例如,应用程序可能在一个 Activity 中包含一个用户列表,当由于配置更改导致 Activity 重新创建时,新的 Activity 必须重新获取用户列表。对于简单的数据,Activity 可以使用 onSaveInstanceState() 方法在 onCreate() 中恢复数据,但这种方法只适用于少量的、可以序列化和反序列化的数据,不适用于像用户列表或位图这样的大量数据。
另一个问题是 UI 控制器经常需要异步调用,这可能需要一些时间来返回结果。UI 控制器需要管理这些调用,并确保系统在销毁后清除它们,以避免潜在的内存泄漏。这种管理需要大量的维护,并且在配置改变导致对象重新创建的情况下,会导致资源的浪费,因为对象可能不得不重新做它已经做过的事情。
UI控制器(如 Activity/ Fragment)主要用于显示 UI 数据、响应用户操作或处理操作系统通信(如权限请求)。如果界面控制器也负责从数据库或网络加载数据,就会导致这个类变的臃肿。将过度的责任分配给 UI 控制器可能导致一个单独的类试图处理所有应用程序的工作,而不是将工作委托给其他类,以这种方式将过度的责任分配给UI控制器也会使测试变得更加困难。
从UI控制器逻辑中分离出视图数据所有权,使开发更容易、更高效。
实现一个ViewModel
ViewModel 对象会在配置发生变化时自动保留,所以他们持有的数据对于新创建的 Activity 或 Fragment 实例是立即可用的。例如,如果需要在应用程序中显示一个用户列表,确保将获取和保存用户列表的职责分配给一个 ViewModel,而不是一个 Activity 或 Fragment,通过下面的示例代码说明:
public class UserListViewModel extends ViewModel {
private MutableLiveData> users;
public LiveData> getUsers() {
if (users == null) {
users = new MutableLiveData<>();
loadUsers();
}
return users;
}
private void loadUsers() {
new Thread(){
@Override
public void run() {
super.run();
List list = new ArrayList<>(20);
for (int i = 0; i < 20; i++) {
SystemClock.sleep(1000);
User user = new User(String.valueOf(i));
list.add(user);
}
users.postValue(list);
}
}.start();
}
}
然后可以从一个 Activity 中访问该列表,如下:
public class UserListActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
......
UserListViewModel userListViewModel = ViewModelProviders.of(this).get(UserListViewModel.class);
userListViewModel.getUsers().observe(this, new Observer>() {
@Override
public void onChanged(@Nullable List users) {
adapter.setUsers(users);
}
});
}
}
如果 Activity 被重新创建,它会接收到被第一个 Activity 创建的相同的 UserListViewModel 实例。当宿主 Activity 结束运行时,Framework 层会调用 ViewModel 对象的 onCleared() 方法从而清理资源。
注意:ViewModel 唯一的责任是管理 UI 数据。一个 ViewModel 绝不能引用视图,有生命周期的,或持有 Activity 的 context 引用的类。
ViewModel 是比 View 或 LifecycleOwner 更具体的实例。这样的设计也意味着可以更容易的测试 ViewModel ,因为它不引用 View 和具有生命周期的对象。ViewModel 对象可以包含 LifecycleObserver 对象(如 LiveData)。然而ViewModel 对象禁止观察具有生命周期的可观察者的变化(如 LiveData)。如果ViewModel 需要 Application context,例如创建一个系统的服务,因为 Application 是 Context 的子类,所以可以继承 AndroidViewModel 类,并创建一个构造函数接收构造函数中的 Application。
ViewModel 的生命周期
ViewModel 对象通过 ViewModelProvider 获取,并且作用域在一个具有生命周期的类上。ViewModel 在其具有生命周期的类存活时,会一直保留在内存中:在一个 Activity 中,会保存到 finish 之前;而在一个 Fragment,会保存到 detached 之前。
下图展示了一个 Activity 经过旋转然后到结束运行的各种生命周期状态。该插图还在 Activity 的生命周期旁边显示了 ViewModel 的生命周期。虽然这个图描述的是 Activity 的状态,但是 Fragment 是和它类似的。
从图中可以看出通常只需要在系统第一次调用 Activity 对象的 onCreate() 方法时获取一个 ViewModel。系统可能在一个 Activity 的运行过程中,会多次调用 onCreate() 方法(比如当设备屏幕旋转时)。我们第一次请求的 ViewModel 会一直存在,直到 Activity 结束并销毁。这意味着一个 ViewModel 将不会因为它的创建者的一个配置变化而被销毁(例如:屏幕旋转)。Activity 的新实例将与现有的 ViewModel 重新连接。
Fragment 之间共享数据
一个 Activity 中的两个或多个 Fragment 需要相互通信是很常见的。设想有两个 Fragment,其中有一个用来展示一个列表,用户可以从列表中选择一个条目,另一个 Fragment 显示所选项目的内容。这种情况绝不是微不足道的,因为这两个Fragment 都需要定义一些接口描述,而宿主 Activity 必须将两者结合起来。此外,这两个 Fragment 必须处理另一个 Fragment 尚未创建或可见时的场景。
现在,可以通过使用 ViewModel 对象处理这个难点。可以通过在这些 Fragment 的宿主 Activity 中共享一个 ViewModel,来处理这种通信,通过下面的示例代码说明:
public class SharedViewModel extends ViewModel {
private final MutableLiveData selected = new MutableLiveData<>();
private final MutableLiveData> liveData = new MutableLiveData<>();
public void select(String string) {
selected.setValue(string);
}
public LiveData getSelected() {
return selected;
}
public LiveData> getLiveData() {
List list = new ArrayList<>();
for (int i = 0; i < 10; i++) {
list.add(new User(String.valueOf(i)));
}
liveData.setValue(list);
select(list.get(0).getFirstName());
return liveData;
}
}
public class MasterFragment extends Fragment {
private SharedViewModel model;
private MyAdapter mAdapter;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
model.getLiveData().observe(this, new Observer>() {
@Override
public void onChanged(@Nullable List users) {
mAdapter.setUsers(users);
}
});
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View root = inflater.inflate(R.layout.master_fragment, container, false);
RecyclerView recyclerView = root.findViewById(R.id.recycler);
recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
mAdapter = new MyAdapter();
recyclerView.setAdapter(mAdapter);
mAdapter.setOnItemClickListener(new OnItemClickListener() {
@Override
public void onItemClick(String string) {
model.select(string);
}
});
return root;
}
}
public class DetailFragment extends Fragment {
private TextView textView;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
SharedViewModel model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
model.getSelected().observe(this, new Observer() {
@Override
public void onChanged(@Nullable String s) {
textView.setText(s);
}
});
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View root = inflater.inflate(R.layout.detail_fragment, container, false);
textView = root.findViewById(R.id.text);
return root;
}
}
注意: 这两个 Fragment 使用 getActivity() 获取 ViewModelProvider。这样,两个 Fragment 得到宿主 Activity 中的同一个 SharedViewModel 实例。
这种方法的好处:
- 这个 Activity 不需要做任何事情,也不需要知道任何关于这个通信的事情。
- Fragment 之间不需要知道除了 SharedViewModel 之外的逻辑。如果其中一个 Fragment 消失了,不会影响另一个 Fragment 继续工作。
- 每个 Fragment 都有自己的生命周期,不受另一个 Fragment 生命周期的影响。如果一个 Fragment 替换另一个 Fragment,UI 会继续工作,没有任何影响。
使用 ViewModel 替换数据加载器
像 CursorLoader 这样的加载类,经常被用来在一个应用程序的 UI 与数据库之间保持数据同步。现在可以使用 ViewModel 与其他几个类来取代这个加载类。使用 ViewModel 可以将 UI 控制器与数据加载操作分离,这意味着有更少的类之间的强引用。
使用加载器的一种通用方法是,一个应用程序可能使用 CursorLoader 观察数据库的内容。当数据库中的值发生变化时,加载器会自动触发重新加载数据并更新UI:
使用 ViewModel、Room、LiveData 一起取代加载器。ViewModel 确保数据在设备配置变化时不被销毁。Room 会在数据库变化时通知 LiveData,反过来,Livedata 会在数据修改后更新UI。
这篇文章介绍了如何使用一个 ViewModel 与 LiveData 取代 AsyncTaskLoader。
https://medium.com/google-developers/lifecycle-aware-data-loading-with-android-architecture-components-f95484159de4
当数据变得更复杂时,可以选择单独的类来加载数据。ViewModel 的目的是封装UI 控制器的数据,保证数据在设备配置变化时不被销毁。
参考文章
https://developer.android.com/topic/libraries/architecture/viewmodel.html