1 ViewModel的作用
从ViewModel的实现原理来看,ViewModel主要是用于解决一些界面上的“临时”、“瞬态”数据存储的问题,例如,界面上有一个EditText,用户在里面已经输入了一些内容,但是此时如果设备发生了诸如旋转屏幕等操作,此时如果不进行状态保存操作(也就是我们常说的onSaveInstanceState()),由于Activity的重新创建,这部分数据都会丢失。换句话说,这些数据,和Activity的生命周期“绑定”了,如果想要持久化这些数据,就需要进行手动管理。
因此,谷歌推出了ViewModel来解决以上问题。需要注意的是,Android已经为很多控件在以上情况发生时进行状态保存,例如,EditText就会在屏幕发生旋转时自动保存内容,并恢复它。主要实现也是借onSaveInstanceState()和onRestoreInstanceState()这两个方法来实现的,Activity在发生旋转或者其他异常销毁的情况下,会调用自身onSaveInstanceState()方法来进行状态保存,在这个方法的默认实现中,会遍历Activity中所有的View,并依次调用其onSaveInstanceState()方法进行状态保存。同理,Activity会在重新创建时调用onRestoreInstanceState()方法来进行状态恢复,在这个方法的默认实现中,会遍历Activity中所有的View,并调用其的onRestoreInstanceState()方法。View可以重写这两个方法来实现状态保存和恢复(对于一些自定义的View,我们也可以通过这种方式来实现保存和恢复状态的功能)。不过这些都和ViewModel有着本质的不同,onSaveInstanceState()和onRestoreInstanceState()方法本质是通过序列化,将“状态”保存到文件中,再从文件中恢复它们。而ViewModel是通过应用运行时的在内存中的实例对象来保存这些数据。从性能上,ViewModel应该是具有更好的性能和响应速度。而onSaveInstanceState()和onRestoreInstanceState()方法则更适合一些少量的,支持序列化的数据的保存,且其支持数据的持久化,我们可以在这两个方法中进行一些数据持久化的操作。
2 ViewModel实战
通过一个案例来演示一下ViewModel的效果,我编写了一个简单的程序,界面上存在一个显示数字的TextView和Button,点击Button,会给TextView中的数字加1。
2.1 不添加任何额外操作
在未使用onSaveInstanceState()和onRestoreInstanceState()方法进行状态保存恢复或未使用ViewModel的情况下,效果图如下图所示,TextView中的数字在发生屏幕旋转时会被重置。
代码很简单,如下:
package com.android.viewmodeldemo;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.Button;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity {
private Button mBtnPlus;
private TextView mNumCount;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mBtnPlus = findViewById(R.id.btn_plus);
mNumCount = findViewById(R.id.num_count_textview);
mBtnPlus.setOnClickListener(v -> {
plusNumCount();
});
}
private void plusNumCount() {
mNumCount.setText(String.valueOf(Integer.valueOf(mNumCount.getText().toString()) + 1));
}
}
2.2 借助ViewModel
通过将需要独立于Activity生命周期的数据包装到ViewModel中,我们实现了将这部分数据从Activity中“独立”出来,也就是说,这里说的“独立”并非是指Activity不再持有这部分数据,而是指数据不再随着Activity的生命周期而重置或者改变,避免了我们主动的去保存、管理它们的“状态”。借助ViewModel,效果图如下所示,TextView中的数字在发生屏幕旋转时不会被重置。
代码如下,ViewModel类:
package com.android.viewmodeldemo;
import androidx.lifecycle.ViewModel;
public class NumCountViewModel extends ViewModel {
private int mNumCount;
public int getNumCount() {
return mNumCount;
}
public void setNumCount(int numCount) {
mNumCount = numCount;
}
}
Activity中的逻辑:
package com.android.viewmodeldemo;
import androidx.appcompat.app.AppCompatActivity;
import androidx.lifecycle.ViewModelProvider;
import android.os.Bundle;
import android.widget.Button;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity {
private Button mBtnPlus;
private TextView mNumCount;
private NumCountViewModel mNumCountViewModel;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mBtnPlus = findViewById(R.id.btn_plus);
mNumCount = findViewById(R.id.num_count_textview);
// 1 创建ViewModel对象
mNumCountViewModel = new ViewModelProvider(this, new ViewModelProvider.AndroidViewModelFactory(getApplication()))
.get(NumCountViewModel.class);
// 2 为TextView赋值
mNumCount.setText(String.valueOf(mNumCountViewModel.getNumCount()));
mBtnPlus.setOnClickListener(v -> {
plusNumCount();
});
}
private void plusNumCount() {
mNumCountViewModel.setNumCount(mNumCountViewModel.getNumCount() + 1);
mNumCount.setText(String.valueOf(mNumCountViewModel.getNumCount()));
}
}
从具体代码中看,ViewModel类并无什么特殊的逻辑,主要是作为数据的包装类而存在。在ViewModel实现原理分析中,我们可以看到,ViewModel实际上还是被存储到ViewModelStoreOwner中,本样例中,Activity充当了这个Owner对象,进而间接的存储到ActivityThread类中。
2.3 借助onSaveInstanceState()和onRestoreInstanceState()
借助这两个方法,我们也可以实现在Activity异常销毁时保存我们数据状态,效果和ViewModel一样,如下图
代码如下:
package com.android.viewmodeldemo;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.Button;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity {
private static final String KEY_NUM_COUNT = "key_num_count";
private Button mBtnPlus;
private TextView mNumCount;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mBtnPlus = findViewById(R.id.btn_plus);
mNumCount = findViewById(R.id.num_count_textview);
mBtnPlus.setOnClickListener(v -> {
plusNumCount();
});
}
private void plusNumCount() {
mNumCount.setText(String.valueOf(Integer.valueOf(mNumCount.getText().toString()) + 1));
}
@Override
protected void onSaveInstanceState(@NonNull Bundle outState) {
super.onSaveInstanceState(outState);
outState.putString(KEY_NUM_COUNT, mNumCount.getText().toString());
}
@Override
protected void onRestoreInstanceState(@NonNull Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
if (savedInstanceState != null) {
String savedNumCount = savedInstanceState.getString(KEY_NUM_COUNT);
mNumCount.setText(savedNumCount);
}
}
}
从代码量上看,借助ViewModel和借助onSaveInstanceState()以及onRestoreInstanceState()差不多,甚至ViewModel的代码好像还要多一些,毕竟要单独创建一个ViewModel类,但是,这里是只有一个TextView的场景。如果有大量需要保持状态的数据,我们都可以放在ViewModel中,借助Generate功能为它们生成getter和setter方法即可,无需在onSaveInstanceState()以及onRestoreInstanceState()中一个一个的保存了。
并且前面也提到过,onSaveInstanceState()以及onRestoreInstanceState()主要是用于存储一些少量的(因为涉及到IO,所以如果是大量数据会影响到性能),支持序列化的数据,它可以将数据持久化到存储空间中。而ViewModel支持所有类型的数据,它本质是将数据保存到内存中,借助ActivityThread维持其状态。