Architecture Components 知识梳理(3) - ViewModel 示例

一、概述

在第二章的内容中,我们使用ViewModel作为存储LiveData的容器,其实它的功能要比我们看到的更为强大,其应用场景有如下几点。

1.1 应用场景

1.1.1 恢复 re-create 前的数据

在处理屏幕旋转所导致的Activity重启时,我们期望可以保证数据不丢失。在之前,如果我们希望能够恢复这种re-create行为所丢失的数据,那么就需要继承onSaveInstanceState()方法,将数据进行序列化存储在bundle中,之后在onCreate(Bundle savedInstanceState)方法中从bundle中取出数据。

1.1.2 维护异步回调

在日常的业务中,往往需要发起许多异步的操作,并等待其返回的结果。这时候我们就需要去维护这些逻辑,避免在destroy后,回调还一直持有Activity的实例,导致其不能回收,发生内存泄露。

1.1.3 简单的 UI 组件

可以看到,在UI组件中包含了过多的逻辑,但其实UI组件的职责应当是很简单的:展示数据响应用户的行为,其它的逻辑都应当放在UI组件之外。

1.2 目录框架

我们将从下面几个方面对ViewModel做一个初步的了解:

  • 使用ViewModel存储数据,并处理屏幕旋转
  • 了解ViewModel的生命周期
  • 通过ViewModel,处理Fragment之间的交互
  • 使用ViewModel替换loader

二、使用 ViewModel 存储数据,并处理屏幕旋转

Architecture Components 知识梳理(2) - LiveData 示例 中我们演示了如何在ViewModel中存储数据,请大家先看一下前一篇文章中的内容:

/**
 * LiveData 学习 Demo。
 */
public class LiveDataActivity extends AppCompatActivity {

    private Button mBtnRefresh;
    private TextView mTvResult;
    private DataViewModel mViewModel;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_live_data);
        mTvResult = findViewById(R.id.tv_result);
        //1.创建 ViewModel。
        mViewModel = ViewModelProviders.of(this).get(DataViewModel.class);
        Log.d("DataViewModel", "createViewModel, model_address=" + mViewModel);
        //2.添加观察者。
        mViewModel.getWatcher().observeForever(new Observer<List<String>>() {

            @Override
            public void onChanged(@Nullable List<String> strings) {
                Log.d("DataViewModel", "onChanged");
                String tvDisplay = "";
                for (String result : strings) {
                    tvDisplay += (result + "\n");
                }
                //4.数据发生了改变后会回调到这里。
                mTvResult.setText(tvDisplay);
            }
        });
        mBtnRefresh = findViewById(R.id.btn_refresh);
        mBtnRefresh.setOnClickListener(new View.OnClickListener() {

            @Override
            public void onClick(View v) {
                //3.触发加载。
                Log.d("DataViewModel", "mViewModel.load()");
                mViewModel.load();
            }
        });
    }

    @Override
    protected void onResume() {
        super.onResume();
        Log.d("DataViewModel", "onResume()");
    }

    @Override
    protected void onPause() {
        super.onPause();
        Log.d("DataViewModel", "onPause()");
    }
}

普通的场景在之前已经说明过了,下面我们来看一下在 旋转屏幕 之后会发生什么。首先,我们进入界面,触发加载数据的操作,log如下所示:

触发加载数据的操作

接着,我们旋转屏幕,这将会触发Activity的重启,红色部分是触发重启后的操作:

重启后的操作

这里我们可以发现两个现象:

  • 新的Activity重新走了onCreate方法,但是通过ViewModelProvider.of方法获得的ViewModel和之前仍然是同一个实例。
  • 重启之后,触发了observeronChanged()方法,界面刷新后,又获得了上次保存的数据。

可以看到,并不需要做什么序列化的操作,也不需要进行手动的赋值操作,ViewModel已经为我们处理好了。

三、ViewModel 的生命周期

ViewModel 生命周期

ViewModel的生命周期是和我们通过ViewModelProvider.of操作符传入的UI组件(Activity/Fragment)绑定的,ViewModel的实例将一直存在内存当中,直到UI组件被销毁的时候ViewModel也随之销毁。

对于Activity来说是其finishes的时候,而对于Fragment,则是其detached的时候。

四、在 Fragment 之间共享数据

当我们希望在Fragment之间进行交互或者共享数据的时候,通常都是通过在它们共同的宿主Activity声明接口,再由Activity找到目标Fragment去通知它。

当使用ViewModel后,这一交互过程将会很简单。只需要让两个Fragment都共享同一个ViewModel(也就是说,ViewModelProvider.of(x)传入的是它们共同的宿主Activity),当一方的数据改变后,改变ViewModel中的LiveData,其它观察者就会收到通知,再去进行界面的刷新就可以了。

五、使用 ViewModel 替换 Loader

很久以前我们学习了Loader的实现原理,Loader 知识梳理(1) - LoaderManager初探,它同样可以在屏幕旋转的时候进行自动的数据恢复,并通过CursorLoader在数据发生变化的时候自动去重新加载数据。

使用 Loader 加载数据

现在我们已经可以使用ViewModel+LiveData+Room的方式来替换Loader框架了:

  • ViewModel:负责在UI重建的时候保持数据不丢失。
  • Room:在数据库内容发生变化的时候通知LiveData
  • LiveData:在LiveData的内容发生变化的时候通知UI刷新。
使用 ViewModel 加载数据

六、参考文献

(1) Handling lifecycles with lifecycle-aware components

推荐阅读更多精彩内容