在应用配置发生改变时,比如屏幕旋转、语言切换等,Activity/Fragment就可能被销毁,当再次启动时,Activity/Fragment上的数据就可能丢失,而ViewModel可以保存这些数据,并且在应用配置发生改变时,ViewModel对象会保留下来,进而ViewModel保存的数据可以供下一个Activity/Fragment使用,所以在处理页面上的数据时,确保将这些数据保存到ViewModel对象中去。
下图动画可以看出,在屏幕旋转之后,原先的1变成了最初的0,说明数据丢失了。
我们需要自定义一个继承于ViewModel的类,然后在里面声明需要保存的值。
public class MyViewModel extends ViewModel {
int num = 0;
}
然后再MainActivity.java中获取自定义ViewModel的实例。
public class MainActivity extends AppCompatActivity {
private TextView textView;
private Button button;
private ActivityMainBinding binding;
private MyViewModel viewModel;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textView = (TextView) findViewById(R.id.textView);
button = (Button) findViewById(R.id.button);
viewModel = new ViewModelProvider(this).get(MyViewModel.class);
textView.setText(String.valueOf(viewModel.num));
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
viewModel.num = viewModel.num + 1;
textView.setText(String.valueOf(viewModel.num));
}
});
}
}
形成的效果如下,可以看到在屏幕旋转之后,TextView中的内容没有发生改变。
LiveData可以用来监听数据的变化,进而UI界面可以作出响应。下面对自定义ViewModel做出改变。
这里我们将需要保存的数据定义为MutableLiveData类型,而不是LiveData,因为LiveData没有对外公开数据,而MutableLiveData是LiveData的子类,可以通过setValue和getValue来设置和修改数据。
public class MyViewModel extends ViewModel {
public MutableLiveData<Integer> currentNum;
public MutableLiveData<Integer> getCurrentNum() {
if(currentNum == null) {
currentNum = new MutableLiveData<Integer>();
currentNum.setValue(0); // 必须在创建实例或设置初值,否则在调用getValue时可能发生空指针异常
}
return currentNum;
}
public void add(int num) {
currentNum.setValue(currentNum.getValue() + num);
}
}
根据MyViewModel实例获取MutableLiveData实例,然后调用observe方法,这个方法就是实现了对数据的监听,一旦数据发生改变,onChanged方法将会执行。
viewModel.getCurrentNum().observe(this, new Observer<Integer>() {
@Override
public void onChanged(Integer integer) {
// TODO 视图响应
textView.setText(String.valueOf(integer));
}
});
MainActivity.java
public class MainActivity extends AppCompatActivity {
private TextView textView;
private Button button;
private ActivityMainBinding binding;
private MyViewModel viewModel;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textView = (TextView) findViewById(R.id.textView);
button = (Button) findViewById(R.id.button);
viewModel = new ViewModelProvider(this).get(MyViewModel.class);
viewModel.getCurrentNum().observe(this, new Observer<Integer>() {
@Override
public void onChanged(Integer integer) {
textView.setText(String.valueOf(integer));
}
});
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
viewModel.add(1);
}
});
}
}
使用DataBinding可以直接在XML文件中修改文本等组件的内容。具体使用方法如下:
首先在app/build.gradle文件内的defaultConfig中加入如下代码。
dataBinding {
enabled true
}
然后再需要进行数据绑定的XML文件中按照如下方法编写代码。
其中variable标签中的type是类名,类名必须是完整的,name是变量名,指向了这个类。具体使用方法是:
@{},然后再大括号内编写类似于Java的代码,比如:
android:text="@{String.valueOf(data.getCurrentNum)}"
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="data"
type="com.example.viewmodeltest.MyViewModel"
/>
data>
layout>
完整代码:
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="data"
type="com.example.viewmodeltest.MyViewModel"
/>
data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="增加"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{String.valueOf(data.getCurrentNum)}"
android:textSize="24sp"
app:layout_constraintBottom_toTopOf="@+id/button"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.503"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.819" />
androidx.constraintlayout.widget.ConstraintLayout>
layout>
然后在MainActivity类中获取DataBinding实例,其中ActivityMainBinding,这是xml文件名+Binding的形式。
ActivityMainBinding binding = DataBindingUtil.setContentView(this,R.layout.activity_main);
binding.setData(viewModel);
binding.setLifecycleOwner(this);
完整代码:
public class MainActivity extends AppCompatActivity {
private TextView textView;
private Button button;
private ActivityMainBinding binding;
private MyViewModel viewModel;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = DataBindingUtil.setContentView(this,R.layout.activity_main);
textView = (TextView) findViewById(R.id.textView);
button = (Button) findViewById(R.id.button);
viewModel = new ViewModelProvider(this).get(MyViewModel.class);
binding.setData(viewModel);
binding.setLifecycleOwner(this);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
viewModel.add(1);
}
});
}
}
下面先演示进程被杀死后,页面中数据的变化。
为了实现这个进程被杀死,我们需要进入开发者模式,然后在设置中开启Don’t keep Activities选项,这样只要app进入后台就一定会被杀死。
可以看到,应用进程被删除之后,数据没有保持删除前的内容,而是变成了最初的0。
下面继续对MyViewModel进行修改,以键值对的形式将数据存入到SavedStateHandle实例中去。
public class MyViewModel extends ViewModel {
private SavedStateHandle handle;
static final String KEY = "KEY";
public MyViewModel(SavedStateHandle handle) {
this.handle = handle;
}
public MutableLiveData<Integer> getCurrentNum() {
// 这些可以防止进程被杀死是数据丢失
if(!handle.contains(KEY)){
handle.set(KEY,0);
}
return handle.getLiveData(KEY);
}
public void add(int num) {
getCurrentNum().setValue(getCurrentNum().getValue() + num);
}
}
在MainActivity类中唯一的修改就是修改获取MyViewModel实例的方法。
viewModel = new ViewModelProvider(this,new SavedStateViewModelFactory(getApplication(),this)).get(MyViewModel.class);
最终效果如下,数据在进程被杀死后依然保持不变。