一.视图和数据模型之间的桥梁ViewModel
在页面(Activity/Fragment)功能较为简单的情况下,通常会把UI交互,与数据获取等相关的业务逻辑全部写在页面中。但是在页面功能复杂的情况下,这样做是不合适的,因为它不符合“单一功能原则”。页面应该只负责处理用户和UI控件的交互,并将数据展示在屏幕上。与数据相关的业务逻辑应该单独处理和存放。为了更好地将职能划分清楚,Android为我们提供了ViewModel类,专门用于存放应用程序页面所需要的数据。
二.VIewModel的生命周期特性
由于Android平台的特殊性,若应用程序支持横竖屏切换,那么当用户旋转手机屏幕的时候,我们还需要考虑数据的存储和恢复。如果数据不进行存储,则通常还要去重新获取一次数据。幸运的是,ViewModel可以为我们解决这个问题。ViewModel独立于配置变化,这意味着,屏幕旋转所导致的Activity重建,并不会影响ViewModel的生命周期,如下图所示:
三.ViewModel的基本使用方法
前面提到,ViewModel最重要的作用是将视图和数据分离,并独立于Activity的重建。为了验证这一点,我们在ViewModel中创建一个计时器,每隔1s钟,通过接口OnTimeChangeListener通知它的调用者,并通过这个示例来学习一下ViewModel的使用。
第一步,写一个类继承ViewModel,重写onCleared()方法:
代码语言:javascript复制public class TimerViewModel extends ViewModel {
private Timer timer;
private int currentSecond;
private OnTimeChangedListener onTimeChangedListener;
public void startTiming(){
if(timer==null){
timer=new Timer();
TimerTask timerTask=new TimerTask() {
@Override
public void run() {
currentSecond ;
if(onTimeChangedListener!=null){
onTimeChangedListener.onTimeChanged(currentSecond);
}
}
};
timer.schedule(timerTask,1000,1000);
}
}
public interface OnTimeChangedListener{//通过接口的方式,完成对调用者的通知
void onTimeChanged(int second);
}
public void setOnTimeChangedListener(OnTimeChangedListener onTimeChangedListener){
this.onTimeChangedListener=onTimeChangedListener;
}
@Override
protected void onCleared() {//当ViewModel不需要的时候,即与之相关的Activity都被销毁时,该方法会被系统调用
super.onCleared();
timer.cancel();
}
}
第二步,在Activity中监听OnTimeChangeListener发来的通知,并据此更新UI界面。ViewModel的实例化过程是通过ViewModelProvider来完成的,ViewModelProvider会判断ViewModel是否存在,若存在的话直接返回,否则他会创建一个ViewModel。
代码语言:javascript复制public class MainActivity extends AppCompatActivity {
private TextView tv_display;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
iniComponent();
}
public void iniComponent(){
tv_display=findViewById(R.id.tv_display);
TimerViewModel timerViewModel=new ViewModelProvider(this).get(TimerViewModel.class);
timerViewModel.setOnTimeChangedListener(new TimerViewModel.OnTimeChangedListener() {
@Override
public void onTimeChanged(int second) {
runOnUiThread(new Runnable() {
@Override
public void run() {
tv_display.setText(second "");
}
});
}
});
timerViewModel.startTiming();
}
}
运行程序并旋转屏幕。可以看到,当屏幕发生旋转的时候,计时器仍然在继续,这意味着横竖屏下的Activity对应的ViewModel是同一个,它所持有的数据一直存在着。
四.ViewModel和AndroidViewModel
前面提到,ViewModel是独立于配置变化的,它的生命周期比页面长,所以需要注意的一点是:不要向ViewModel传入任何类型的Context或带有Context引用的对象,这可能会导致页面无法被销毁,从而导致内存泄漏。如果一定要这样做,可以使用AndroidViewModel类,它继承自ViewModel,并接收Application作为Context。这意味着它的生命周期和Application是一样的,那么就不算是一个内存泄漏了。
五.ViewModel和onSaveInstanceState()方法
对于页面数据的保存和恢复,也许你有这样的疑问,onSaveInstanceState()方法同样可以解决屏幕旋转带来的数据丢失问题,那么是不是没必要使用ViewModel呢?好问题!但是要注意,onSaveInstanceState()方法只能存储少量的,能支持序列化的数据,而ViewModel没有这个限制,ViewModel支持页面中的所有数据。但同样需要注意的是,ViewModel不支持数据的持久化,当界面被彻底销毁时,ViewModel及其持有的数据就不存在了,但是onSavaInstanceState()方法没有这个限制,它可以持久化页面的数据。可见,onSaveInstanceState()方法有其特殊的用途,二者不可混淆。