ViewModel
如果系统销毁或重新创建Activity
或者fragment
,则存储在其中的任何瞬态界面相关数据都会丢失。例如,应用的某个 Activity
中可能包含用户列表。因配置更改(如旋转屏幕,分辨率改变等)而重新创建 Activity
后,新 Activity
必须重新提取用户列表。对于简单的数据,activity
可以使用 onSaveInstanceState()
方法从 onCreate()
中的捆绑包恢复其数据,但此方法仅适合可以序列化再反序列化的少量数据,而不适合数量可能较大的数据,如用户列表或位图。诸如activity
和fragment
之类的界面控制器主要用于显示界面数据、对用户操作做出响应或处理操作系统通信(如权限请求)。如果要求界面控制器也负责从数据库或网络加载数据,那么会使类越发膨胀。
使用
ViewModel
可以做到以生命周期形式管理界面数据。将视图和数据分离。架构组件为界面控制器提供了 ViewModel 辅助程序类,该类负责为界面准备数据。在配置更改期间会自动保留 ViewModel 对象,以便它们存储的数据立即可供下一个 activity 或 fragment 实例使用。实现数据共享。
不使用ViewModel
实现一个小功能,点击按钮,textview的数字加1
public class ViewModelStudy extends AppCompatActivity {
private TextView tv;
private int i = 0;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_view_model_study);
tv = findViewById(R.id.tv_test);
findViewById(R.id.btn).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
tv.setText(Integer.toString(i));
i++;
}
});
}
}
但是如果在这过程中屏幕发生旋转,那么显示的值会变为最开始的,而不是和旋转之前的一样。因为屏幕发生了旋转,所以activity
重新经历了onCrea
t的生命周期。不使用ViewModel
的话,界面数据不会保存。ViewModel
的生命周期如下。可以看到ViewModel
的生命周期比activity
的时间更长。ViewModel
对象存在的时间范围是获取 ViewModel
时传递给 ViewModelProvider
的 Lifecycle
。ViewModel
将一直留在内存中,直到限定其存在时间范围的 Lifecycle
永久消失:对于 Activity
,是在 Activity
完成时;而对于 Fragment
,是在 Fragment
分离时。取决于Lifecycle
使用ViewModel
首先继承ViewModel
,数据成员是需要保存的界面数据。
// TestViewModel .java
public class TestViewModel extends ViewModel {
public int getNum() {
return num;
}
public void setNum(int num) {
this.num = num;
}
private int num = 0;
}
//ViewModelStudy.java
public class ViewModelStudy extends AppCompatActivity {
private TextView tv;
private TestViewModel testViewModel;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_view_model_study);
tv = findViewById(R.id.tv_test);
testViewModel = new ViewModelProvider(this).get(TestViewModel.class);
findViewById(R.id.btn).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
int num = testViewModel.getNum();
tv.setText(Integer.toString(num));
testViewModel.setNum(++num);
}
});
}
}
上面这种方式可以实现界面数据的保存,但是旋转之后还是会变为最开始的样子。按下按钮之后数字是旋转前的数字加1,实现了数据的保存。为了严格的实现旋转后数字不变,那么需要使用Livedata
,这也是ViewModel
一般结合Livedata
使用的原因。LiveData
的优势,数据始终保持最新状态如果生命周期变为非活跃状态,它会在再次变为活跃状态时接收最新的数据。例如,曾经在后台的 Activity
会在返回前台后立即接收最新的数据。适当的配置更改,如果由于配置更改(如设备旋转)而重新创建了 activity
或 fragment
,它会立即接收最新的可用数据。
public class TestViewModel extends ViewModel {
private MutableLiveData<Integer> num;
public MutableLiveData<Integer> getNum() {
if (num == null)
num = new MutableLiveData<Integer>(0);
return num;
}
public void setNum(Integer num) {
this.num.setValue(num);
}
}
public class ViewModelStudy extends AppCompatActivity {
private TextView tv;
private TestViewModel testViewModel;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_view_model_study);
tv = findViewById(R.id.tv_test);
testViewModel = new ViewModelProvider(this).get(TestViewModel.class);
testViewModel.getNum().observe(this,
new Observer<Integer>() {
@Override
public void onChanged(Integer integer) {
tv.setText(integer.toString());
}
}
);
findViewById(R.id.btn).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Integer num = testViewModel.getNum().getValue();
testViewModel.setNum(++num);
}
});
}
}
使用ViewModel进行Fragment之间的通信
Activity
中的两个或更多 Fragment
需要相互通信是一种很常见的现象。一般情况这两个 Fragment
都需要定义某种接口描述,并且所有者 Activity
必须将两者绑定在一起。此外,这两个 Fragment
都必须处理另一个 Fragment
尚未创建或不可见的情况。可以使用 ViewModel
对象解决这一常见的难点。这两个Fragment
可以使用其 Activity
范围共享 ViewModel
来处理此类通信。
public class SharedViewModel extends ViewModel {
private final MutableLiveData<Integer> selected = new MutableLiveData<Integer>();
public void select(Integer item) {
selected.setValue(item);
}
public LiveData<Integer> getSelected() {
return selected;
}
}
public class ListFragment extends Fragment {
private SharedViewModel model;
public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
// 它们会收到相同的 SharedViewModel 实例(其范围限定为该 Activity)
//因为这里指定为requireActivity()
model = new ViewModelProvider(requireActivity()).get(SharedViewModel.class);
itemSelector.setOnClickListener(item -> {
model.select(item);
});
}
}
public class DetailFragment extends Fragment {
public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
//它们会收到相同的 SharedViewModel 实例(其范围限定为该 Activity),
//因为这里指定为requireActivity()
SharedViewModel model = new ViewModelProvider(requireActivity()).get(SharedViewModel.class);
model.getSelected().observe(getViewLifecycleOwner(), item -> {
// Update the UI.
});
}
}
Activity
不需要执行任何操作,也不需要对此通信有任何了解。除了 SharedViewModel
约定之外,Fragment
不需要相互了解。如果其中一个 Fragment
消失,另一个 Fragment
将继续照常工作。每个 fragment
都有自己的生命周期,而不受另一个 fragmen
t 的生命周期的影响。如果一个fragment
替换另一个 fragment
,界面将继续工作而没有任何问题。
通信实例
// LeftFragment.java
public class LeftFragment extends Fragment {
private TestViewModel model;
private TextView tv;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_left, container, false);
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
model = new ViewModelProvider(requireActivity()).get(TestViewModel.class);
tv = view.findViewById(R.id.tv_test);
view.findViewById(R.id.addone).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Integer num = model.getNum().getValue();
model.setNum(++num);
}
});
model.getNum().observe(getViewLifecycleOwner(), new Observer<Integer>() {
@Override
public void onChanged(Integer integer) {
tv.setText(integer.toString());
}
});
}
}
//RightFragment.java
public class RightFragment extends Fragment {
private TextView tv;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_right, container, false);
}
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
TestViewModel model = new ViewModelProvider(requireActivity()).get(TestViewModel.class);
tv = view.findViewById(R.id.tv_test);
view.findViewById(R.id.addone).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Integer num = model.getNum().getValue();
model.setNum(++num);
}
});
model.getNum().observe(getViewLifecycleOwner(), new Observer<Integer>() {
@Override
public void onChanged(Integer integer) {
tv.setText(integer.toString());
}
});
}
}