上一篇文章中我们介绍了Architecture Components中的LifeCycle,LifeCycleOwner及LifeCycleObserver,不知道大家掌握的怎么样?在学习编码的路上,还是要多多实践才可以呢。
接下来我们要介绍的是ViewModel。
ViewModel简介
ViewModel是用来存储和管理生命周期过程敏感的界面数据的一个类,用ViewModel存储的数据可以在应用设置项发生改变时保存下来例如当屏幕旋转。
Android框架层管理UI控件的生命周期比如Activity和Fragment。框架层需要决定在面对用户交互时何时销毁或者重建UI控件,这一过程不是由开发者控制的。
如果系统销毁活着重建UI控件,那么用户的输入数据活着你已缓存的UI数据都会丢失,例如,在你的应用中有一个Activity展示着一个用户列表,当屏幕发生旋转时,Activity被重建,那么新创建的Activity需要再去请求一次用户列表数据。对于一些简单的数据,我们可以使用onSavedInstanceState()方法存储在Bundle中,然后在onCreate()函数中恢复,但是这种情况只适用于少量并且可以被序列化的数据,并不适用于其他数据,例如说一个用户列表或者很多图片。
另一个问题是UI控件需要频繁的发起异步请求并等待返回结果。UI控件需要去管理这些异步请求并保证在它完成后被系统清理掉以避免内存泄漏。这种管理需要大量的耐心和细心,并且在这些对象因为设置改变而重建的情形下,造成了一种资源浪费。
Activity和Fragment这种UI空间只是去展示UI数据,响应用户交互或者处理系统交互,例如说请求权限,UI控件同时也需要负责从数据库或者网络上加载数据,我们应该进行责任分摊,不要为UI控件添加过多的操作,这样会导致一个类去处理一个应用所要处理的工作,让我们的测试工作变得更加艰难。
综合以上几点,Google推出了ViewModel,用于帮助UI控件准备数据,ViewModel会在设置发生变化时自动存储数据,他们所持有的数据可以立刻被新建的Fragment或者Activity复用。
ViewModel的使用
假设我们需要在一个 应用页面内展示一个用户列表,那么我们可以将数据请求操作托管给ViewModel,代码如下:
public class MyViewModel extends ViewModel {
private MutableLiveData<List<User>> users;
public LiveData<List<User>> getUsers() {
if (users == null) {
users = new MutableLiveData<List<Users>>();
loadUsers();
}
return users;
}
private void loadUsers() {
// Do an asyncronous operation to fetch users.
}
}
实现一个ViewModel只需要继承自ViewModel即可,随后我们可以在Activity中访问数据,方式如下:
public class MyActivity extends AppCompatActivity {
public void onCreate(Bundle savedInstanceState) {
MyViewModel model = ViewModelProviders.of(this).get(MyViewModel.class);
model.getUsers().observe(this, users -> {
// update UI
});
}
}
如果这个Activity被重建,那么他会接收到上一个Activity所创建的MyViewModel对象,当持有该ViewModel对象的Activity销毁时,系统会调用ViewModel.onCleared()方法释放资源。
注意ViewModel对象绝不能持有View,LifeCycle或者任何持有Activity 引用的对象
在设计理念上,ViewModel的生命周期独立于View或者LifeCycleOwner,这种设计也意味着你可以更简单的为ViewModel编写测试用例以覆盖ViewModel中的操作。ViewModel可以持有LifeCycleObservers比如说LiveData,如果ViewModel需要使用Application的引用,例如说去获取一个系统服务,此时可以继承自AndroidViewModel,该类有一个构造函数,可以接受Application的引用。
这里我再举一个简单的ViewModel的例子,以便大家更好的理解ViewModel。这个例子是界面上有一个Button和一个TextView,TextView用于记录Button 的点击次数,在屏幕旋转时保持TextView上的次数不变。
首先编写布局文件,代码如下:
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout 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"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.example.code.archicomponentssmaples.MainActivity">
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="0"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="32dp"
android:layout_marginEnd="8dp"
android:layout_marginStart="8dp"
android:text="Click Me"
app:layout_constraintBottom_toTopOf="@+id/textView"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</android.support.constraint.ConstraintLayout>
这里我试用了约束布局,不懂得同学可以自行百度,或者使用LinearLayout/RelativeLayout自己实现即可。
编写ViewModel类,用于持有Button的点击次数,如下:
public class ClickCounterViewModal extends ViewModel {
private int count = 0;
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
}
随后在Activity中使用该ViewModel缓存Button点击次数,代码如下:
public class LifeOwnerActivity extends AppCompatActivity {
private TextView mTextView;
private Button mButton;
private ClickCounterViewModal mClickCounterViewModal;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_life_owner);
mClickCounterViewModal = ViewModelProviders.of(this).get(ClickCounterViewModal.class);
initView();
initData();
}
private void initView(){
mTextView = (TextView)findViewById(R.id.owner_textView);
mButton = findViewById(R.id.owner_Button);
}
private void initData(){
displayClickCount();
mButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mClickCounterViewModal.setCount(mClickCounterViewModal.getCount()+1);
displayClickCount();
}
});
}
private void displayClickCount(){
mTextView.setText(mClickCounterViewModal.getCount()+"");
}
}
如上述代码ViewModel对象需要使用ViewModelProviders.of(Context)
进行初始化。
另外多说一点ViewModel的提出只是为了处理UI相关数据的缓存问题,并不代表它能完全替代onSavedInstanceState()的作用。
在这个例子中ViewModel的工作流程如下图:
ViewModel生命周期
ViewModel对象的生命周期依赖于初始化时ViewModelProvider传入的LifeCycle对象,ViewModel对象会常驻在内存中直到与其对应的LifeCycle被销毁,对于Activity而言,就是当其被finish时,对于Fragment而言就是当它被detach的时候。
下图说明了一个Activity在发生屏幕旋转时自身的生命周期变化以及与其对应的ViewModel的生命周期。
通常情况下,在System调起Activity时,我们在Activity的onCreate()函数内初始化ViewModel对象,随后系统可能多次调用该Activity的onCreate()函数,例如说发生多次屏幕旋转。这种清醒下ViewModel来源于第一次onCreate(),直到调用Activity.finish()时,该ViewModel对象才会被销毁并释放资源。
使用ViewModel在Fragment间共享数据
在我们日常变成生活中,Activity需要与其内部的一个或多个Fragment交互信息的需求比比皆是,假设又这样一种情形,我们有一个Activity页面,左右各一个Fragment,左侧展示列表,右侧展示列表中某一项的详情。这种情形下Fragment中需要定义接口,持有Fragment的Activity需要同时绑定这两个Fragment,并且一个Fragment要处理另一个Fragment没有创建或显示的问题。
这种痛点可以通过ViewModel解决,这两个Fragment可以公用一个ViewModel对象,在Activity内处理,示例代码如下:
public class SharedViewModel extends ViewModel {
private final MutableLiveData<Item> selected = new MutableLiveData<Item>();
public void select(Item item) {
selected.setValue(item);
}
public LiveData<Item> getSelected() {
return selected;
}
}
public class MasterFragment extends Fragment {
private SharedViewModel model;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
itemSelector.setOnClickListener(item -> {
model.select(item);
});
}
}
public class DetailFragment extends Fragment {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
SharedViewModel model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
model.getSelected().observe(this, { item ->
// Update the UI.
});
}
}
如上所示,两个Fragment只需要监听ViewModel中值的变化更新UI即可。
需要注意的是这两个Fragment在ViewModelProvider内传入了getActivity(), 此时该ViewModel对象的生命周期完全依赖于持有两个Fragment的Activity。
这种模式有如下几点好处:
- Activity不需要关注交互中的任何事;
- Fragments彼此不需要知道对方的状态,一个Fragment消失了,另一个仍然可以正常工作;
- 每个Fragment有其独立的生命周期,不会彼此影响,如果一个Fragment被另一个Fragment替换了,UI仍然能正常显示;
使用ViewModel代替Loaders
类似于CursorLoader的加载类经常被频繁的用于异步维护界面和数据库的数据一致,现在你也可以用ViewModel和一些其他的辅助类来实现这种功能了,使用ViewModel可以使我们的界面控制与数据加载解耦。
一种常见的使用CursorLoader监听数据库变化的结构图如下,当一个数据库值发生改变时,加载器会自动触发一个重新加载事件,完成后更新UI。
当ViewModel与Room以及LiveData结合使用时,就可以完全替代加载器的功能,ViewModel保证数据在设置发生改变的过程中不清空,当数据发生改变时,Room通知LiveData,随后使用新的数据更新UI。
当数据变得越来越复杂时,你或许会使用一个单独的类去加载数据,ViewModel的目的就在于保证数据在生命周期变化过程中不被清空,关于LiveData,Room相关的更多详细内容,我们会在下一篇推文中详细介绍,感谢大家阅读。更多详细内容欢迎大家关注我的公众号
[图片上传失败...(image-feea9f-1512483891026)]