ViewModel

发布时间 2023-08-10 10:57:28作者: 故乡的樱花开了

一.视图和数据模型之间的桥梁ViewModel

  在页面(Activity/Fragment)功能较为简单的情况下,通常会把UI交互,与数据获取等相关的业务逻辑全部写在页面中。但是在页面功能复杂的情况下,这样做是不合适的,因为它不符合“单一功能原则”。页面应该只负责处理用户和UI控件的交互,并将数据展示在屏幕上。与数据相关的业务逻辑应该单独处理和存放。为了更好地将职能划分清楚,Android为我们提供了ViewModel类,专门用于存放应用程序页面所需要的数据。

二.VIewModel的生命周期特性

  由于Android平台的特殊性,若应用程序支持横竖屏切换,那么当用户旋转手机屏幕的时候,我们还需要考虑数据的存储和恢复。如果数据不进行存储,则通常还要去重新获取一次数据。幸运的是,ViewModel可以为我们解决这个问题。ViewModel独立于配置变化,这意味着,屏幕旋转所导致的Activity重建,并不会影响ViewModel的生命周期,如下图所示:

               

三.ViewModel的基本使用方法

  前面提到,ViewModel最重要的作用是将视图和数据分离,并独立于Activity的重建。为了验证这一点,我们在ViewModel中创建一个计时器,每隔1s钟,通过接口OnTimeChangeListener通知它的调用者,并通过这个示例来学习一下ViewModel的使用。

  第一步,写一个类继承ViewModel,重写onCleared()方法:

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。

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()方法有其特殊的用途,二者不可混淆。