Jetpack 之 ViewModel 组件介绍

182 阅读4分钟

本文目录

一、什么是 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 需要注意的地方

  1. ViewModel 的唯一职责是管理页面需要的数据(即 UI 需要的数据)。它不应该访问你的 View 结构,也不能持有 Activity/Fragment 的引用。
  2. 在使用 ViewModel 的过程中,千万不要将任何类型的 Context 或带有 Context 引用的对象传入 ViewModel ,因为这样做可能会引发内存泄漏。如果一定要在 ViewModel 中使用 Context,那么建议使用 ViewModel 的子类 AndroidViewModel。

七、参考资料

Google Jetpack 官方文档

慕课专栏:跟架构师学Jetpack

《Android Jetpack 应用指南》叶坤