概述
ViewModel类是被设计用来以可感知生命周期的方式存储和管理 UI 相关数据,
ViewModel中数据会一直存活即使 activity configuration发生变化,比如横竖屏切换的时候。
数据持久化
在ViewModel
出现之前我们可以用activity
的onSaveInstanceState()
机制保存和恢复数据,但缺点很明显,它只适合保存少量的可以被序列化、反序列化的数据,假如我们需要保存是一个比较大的比如List<Bitmap>
这种机制明显就不合适。
解耦
从最早的MVC
到目前流行的MVP、MVVM
目的无非是明确职责,分离UI controller
负担。
UI controller 比如Activity 、Fragment
是设计用来渲染展示数据、响应用户行为、处理系统的某些交互等。如果再要求他去负责加载网络或数据库数据,会让其显得臃肿和难以管理。
所以为了简洁、清爽、丝滑,我们可以分离出数据操作的职责给 ViewModel。
异步回调
通常我们 app 需要频繁异步请求数据,比如调接口请求服务器数据。
当然这些请求的回调都是相当耗时的,之前我们在 Activity 或 fragment里接收这些回调。所以不得不考虑潜在的内存泄漏情况,比如 Activity 被销毁后接口请求才返回。
处理这些问题,会给我们增添好多复杂的工作。但利用 ViewModel 处理数据回调,可以完美的解决此痛点。
注意事项
ViewModel绝对不要持有View,Lifecycle
以及其他任何可能持有Activity Context
的类的引用
ViewModel绝对不能去观察一个生命周期感知的被观察者,例如LiveData
注意在进行Adapter更新时,列表项要单独在Activity中声明,不可直接使用传入的参数,因为对象不同会导致更新不到数据
ViewModelProviders
MyViewModel mViewModel = ViewModelProviders.of(this).get(MyViewModel.class);
ViewModel的实例化并不是通过普通的new来完成的,而是通过ViewModelProviders来完成。它会去判断ViewModel
是否存在,若存在则直接返回,否则它会去创建一个ViewModel
。
之所以这么写,是因为viewModel
的生命周期长于Activity
.如果我们在onCreate()
方法中创建ViewModel
的实例,那么每次onCreate
方法执行的时候,ViewModel
都会创建一个新的实例,当手机屏幕发生旋转的时候,就无法保留其中的数据。
现以弃用此方式,改为
MyViewModel mViewModel = new ViewModelProvider(this).get(MyViewModel.class);
Factory
在大多数情况下,我们需要通过构造函数来传递一些参数,我们可以借助ViwModelProvider.Factory来实现
onCleared
ViewModel是一个抽象类,其中只有一个方法onCleared(),当ViewModel不再被需要的时候,也就是与之相关的Activity都被销毁时,该方法会被系统调用,我们可以在这个方法里面执行一些资源释放的操作,以免内存泄漏。
由于屏幕旋转导致的Activity重建,但该方法不会被调用,只有ViewModel已经没有任何Activity与之有关联,系统才会调用此方法。
public class MyViewModel extends ViewModel
{
/**
* 与之相关的Activity被销毁时,该方法会被系统调用。
* 我们可以在该方法中执行一些资源释放的相关操作
*/
@Override
protected void onCleared()
{
super.onCleared();
}
}
注意:既然ViewModel的销毁是由系统来判断和执行的,那么系统是如何判断的呢?是根据Context引用。因此,我们在使用ViewModel的时候,千万不能从外面传入Activity,Fragment或者View之类的含有Context引用的东西,否则系统会认为该ViewModel还在使用中,从而无法被系统销毁回收,导致内存泄漏的发生。
但如果你希望在ViewModel中使用Context怎么办呢?我们可以使用AndroidViewModel类,它继承自ViewModel,并且接收Application作为Context,既然是Application作为Context,也就意味着,我们能够明确它的生命周期和Application是一样的,这就不算是一个内存泄露了。
AndroidViewModel
如果ViewModel
需要Applicaiton的Context
(为了获取系统服务)则可借助AndroidViewModel
- 自定义一个类,继承自
AndroidViewModel
- 构造器一定要有一个
唯一参数-Application'的对象
ViewModel 生命周期图
数据共享
比如在一个Activity
里有多个fragment
之间需要做某些交互。之前的做法是接口回调,需要统一在Activity
里管理,并且不可避免的fragment
之间还得互相持有对方的引用。耦合度高不说,还需要大量的容错判断(比如对方的fragment
是否还活着)。
activity 与其内部的fragment
可以共用同一个 ViewModel
- Activity 不需要做任何事,甚至不知道这次交互,完美解耦。
- Fragment 只需要与
ViewModel
交互,不需要知道对方Fragment
的状态甚至是否存在,更不需要持有其引用。 - Fragment 生命周期互不影响,甚至
fragment
替换成其他的也不影响这个系统的运作。
ViewModel依托于Activity进行实例化,所以数据共享只能在同一个Activity内进行,跨Activity是不能进行数据共享的,会创建新的实例而导致获取不到前一个的数据。
较好的数据共享实例是Activity与下面的Fragment之间的数据共用,要注意ViewModelProvider的提供即传入的owner
要是同一个Activity
SharedViewModel model = new ViewModelProvider(requireActivity()).get(SharedViewModel.class);
public class SharedViewModel extends ViewModel {
private final MutableLiveData<Item> selected;
public LiveData<Item> getSelected() {
if(selected == null)
selected = new MutableLiveData<>();
return selected;
}
/* 初始化数据或外部绑定数据 */
// 也可以在外部直接使用 sharedViewModel.getSelected().setValue()
public void select(Item item) {
selected.setValue(item);
}
}
/* 可以在其中一个Fragment或者Activity中进行数据的绑定*/
public class MasterFragment extends Fragment {
private SharedViewModel model;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
model = new ViewModelProvider(requireActivity()).get(SharedViewModel.class);
itemSelector.setOnClickListener(item -> {
model.select(item);
});
}
}
/* 在其它Fragment或者Activity中进行数据变更的监听*/
public class DetailFragment extends Fragment {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
SharedViewModel model = new ViewModelProvider(requireActivity()).get(SharedViewModel.class);
model.getSelected().observe(this, { item ->
// Update the UI.
});
}
}
用法实例
添加依赖
//lifecycle
def lifecycle_version = "2.3.1"
implementation "androidx.lifecycle:lifecycle-viewmodel:$lifecycle_version" // ViewModel
implementation "androidx.lifecycle:lifecycle-livedata:$lifecycle_version" // LiveData
加载用户信息列表
创建MyViewModel
必须继承自ViewModel
类
- 将数据相关的逻辑都放置到ViewModel中,如:存放一个用户信息列表
- ViewModel还需要处理加载用户信息列表的逻辑
public class MyViewModel extends ViewModel {
// 1. 用户信息,列表
private MutableLiveData<List<User>> users;
// 2. 获取到用户信息列表
public LiveData<List<User>> getUsers() {
if (users == null) {
users = new MutableLiveData<List<User>>();
loadUsers();
}
return users;
}
// 3. 实际加载用户信息列表,可以从数据库或者网络中
private void loadUsers() {
// Do an asynchronous operation to fetch users.
}
}
注册监听
一定要在onCreate()
中进行处理,当Activity
销毁时framework
会调用ViewModel的onCleared()
方法对资源进行清理
public class MyActivity extends AppCompatActivity {
public void onCreate(Bundle savedInstanceState) {
// 1. 第一次执行会创建ViewModel,Activity重建后调用到该方法会获取到之前的ViewModel
MyViewModel mViewModel = new ViewModelProvider(this).get(MyViewModel.class);
// 2. 注册对LiveData的观察,将本Activity作为观察者
model.getUsers().observe(this, users -> {
// update UI
});
}
}