本文目录
一、什么是 ViewModel
ViewModel 是介于 View(视图)和 Model(数据模型)之间的一个东西。它起到了桥梁的作用,使视图和数据既能够分离开,也能够保持通信。
ViewModel 将页面所需要的数据从页面中剥离出来,页面只需要处理用户交互和展示数据。
ViewModel 一般是通过 LiveData 或 DataBinding 两种方式来通知页面数据发生变化,更新 UI。关于 LiveData 和 DataBinding 我们在后面的文章会介绍,这里知道有这两种方式即可。
二、ViewModel 的生命周期
ViewModel 总是随着 Activity/Fragment 的创建而创建,随着 Activity/Fragment 的销毁而销毁。
不过,ViewModel 的生命周期是独立于配置变化的。如屏幕旋转所导致的 Activity 重建,并不会影响 ViewModel 的生命周期,重建后的 Activity 会重新连接到现有的 ViewModel。
Tips:配置变更主要是指:横竖屏切换、分辨率调整、权限变更、系统字体样式变更、启用多窗口模式...
三、ViewModel 的基本使用方法
步骤一:添加依赖
def lifecycle_version = "2.2.0"
// ViewModel
implementation "androidx.lifecycle:lifecycle-viewmodel:$lifecycle_version"
最新版本号请查看:LifeCycle
步骤二:自定义 TimerViewModel 类,继承 ViewModel
public class TimerViewModel extends ViewModel {
/**
* 清理资源
*/
@Override
protected void onCleared() {
super.onCleared();
timer.cancel();
}
}
ViewModel 本身是一个抽象类,其中只有一个 onCleared 方法,当 ViewModel 不再被需要,即与之相关的 Activity 都被销毁时,系统会调用该方法。我们可以在该方法中执行一些资源释放的相关操作。
步骤三
前文提到,ViewModel 最重要的作用是将视图与数据分离,并独立于 Activity 的重建,为了验证这一点,我们在 ViewModel 中创建一个计时器 Timer,每隔 1s,通过接口 OnTimeChangeListener 通知它的调用者(实际上通过接口的方式通知不是很好,更好的方式是通过 LiveData 组件来实现)。
public class TimerViewModel extends ViewModel {
private Timer timer;
private int currentSecond;
/**
* 开始计时
*/
public void startTiming() {
if (timer == null) {
currentSecond = 0;
timer = new Timer();
TimerTask timerTask = new TimerTask() {
@Override
public void run() {
currentSecond++;
if (onTimeChangeListener != null) {
onTimeChangeListener.onTimeChanged(currentSecond);
}
}
};
timer.schedule(timerTask, 1000, 1000);
}
}
/**
* 通过接口的方式,完成对调用者的通知
* 实际上这种方式不是很友好,更好的方式是通过LiveData组件来实现
*/
public interface OnTimeChangeListener {
void onTimeChanged(int second);
}
private OnTimeChangeListener onTimeChangeListener;
public void setOnTimeChangeListener(OnTimeChangeListener onTimeChangeListener) {
this.onTimeChangeListener = onTimeChangeListener;
}
/**
* 清理资源
*/
@Override
protected void onCleared() {
super.onCleared();
timer.cancel();
}
}
步骤四:在 TimerActivity 中监听 OnTimeChangeListener 发来的通知,并更新 UI。
ViewModel 的实例化过程,是通过 ViewModelProvider 来完成的。ViewModelProvider 会判断 ViewModel 是否存在,若存在直接返回,否则它会创建一个 ViewModel。
public class TimerActivity extends AppCompatActivity {
private TextView textView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_timer);
initComponent();
}
private void initComponent() {
textView = findViewById(R.id.textView);
TimerViewModel timerViewModel = new ViewModelProvider(this).get(TimerViewModel.class);
timerViewModel.setOnTimeChangeListener(new TimerViewModel.OnTimeChangeListener() {
@Override
public void onTimeChanged(int second) {
runOnUiThread(new Runnable() {
@Override
public void run() {
textView.setText("TIME:" + second);
}
});
}
});
timerViewModel.startTiming();
}
}
步骤五:运行项目,并且旋转屏幕,看看计时器有没有停止
当旋转屏幕,Activity 重建时,计时器并没有停止。
四、ViewModel 与 AndroidViewModel
为了避免内存泄漏,我们在使用 ViewModel 时,不能将任何类型的 Context 或含有 Context 引用的对象传入 ViewModel。但是如果希望在 ViewModel 中使用 Context 的话该怎么办呢?
可以使用 AndroidViewModel ,它继承自 ViewModel,并接收 Application 作为 Context。
五、ViewModel 与 onSaveInstanceState() 对比
对于页面数据的保存和恢复,onSaveInstanceState() 方法同样可以解决屏幕旋转带来的数据丢失问题,那么我们是不是就没有必要使用 ViewModel 了呢?
注意,onSaveInstanceState() 方法只能保存少量的、能支持序列化的数据,而 ViewModel 没有这个限制,ViewModel 能支持页面中所有的数据。
同样需要注意的是,ViewModel 不支持数据的持久化,当界面被彻底销毁时,ViewModel 及其持有的数据就不存在了,但是 onSaveInstanceState() 方法没有这个限制,它可以持久化页面的数据。
可见,onSaveInstanceState() 方法有其特殊的用途,二者不可以混淆。
名称 | 保存数据的类型 | 是否支持持久化 |
---|---|---|
ViewModel | 只能保存少量的、能支持序列化的数据 | 否 |
onSaveInstanceState() | 支持页面中所有的数据 | 是 |
六、使用 ViewModel 需要注意的地方
- ViewModel 的唯一职责是管理页面需要的数据(即 UI 需要的数据)。它不应该访问你的 View 结构,也不能持有 Activity/Fragment 的引用。
- 在使用 ViewModel 的过程中,千万不要将任何类型的 Context 或带有 Context 引用的对象传入 ViewModel ,因为这样做可能会引发内存泄漏。如果一定要在 ViewModel 中使用 Context,那么建议使用 ViewModel 的子类 AndroidViewModel。