ViewModel 主要是用来管理UI相关的数据的,使用它有两个优势:
1,可以使 ViewModel 以及 ViewModel 中的数据在屏幕旋转或配置更改引起的 Activity 重建时存活下来, 重建后数据可继续使用
2,可以帮助开发者轻易实现 Fragment 与 Fragment 之间, Activity 与 Fragment 之间的通讯以及共享数据,不同Fragmeng之间的数据共享即持有同一个ViewModel类的引用来操作相关的数据:https://blog.csdn.net/zhuzp_blog/article/details/78910535
在activity或者fragment中获取ViewModel的实例我们需要使用到如下方法:
myViewModel = ViewModelProviders.of(this).get(MyViewModel.class);
这里of里面的this既可以是activity也可以是Fragment,具体请看下面的示例。
1,创建项目,因为ViewModel类为最新jetpack组件中的类,所以需要添加androidx支持,创建项目时注意勾选使用androidx。
2,创建Activity对应的布局文件,本例要实现的案例是一个TextView和两个Button,两个Button分别实现对TextView的+1和-1操作,布局文件如下:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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"
android:orientation="vertical"
android:gravity="center_horizontal"
tools:context=".MainActivity">
<TextView
android:id="@+id/tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="0"
android:textSize="17sp"
android:textColor="#000"
android:padding="20dp"
/>
<Button
android:id="@+id/btn1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="+1"/>
<Button
android:id="@+id/btn2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="-1"/>
</LinearLayout>
3,创建ViewModel来管理我们需要用到的数据,如下:
import androidx.lifecycle.ViewModel;
public class MyViewModel extends ViewModel {
private int number = 0;
public int getNumber() {
return number;
}
public void setNumber(int number) {
this.number = number;
}
// 正数为加,负数为减
public void doSet(int n){
number+=n;
}
@Override
protected void onCleared() {
super.onCleared();
// 当activity或者fragment销毁时会调用该方法
}
}
注意:继承的是androidx下的ViewModel类;
4,创建Activity,在activity中使用ViewModel中管理的数据,如下:
import androidx.lifecycle.ViewModelProviders;
public class MainActivity extends AppCompatActivity {
private MyViewModel myViewModel;
private Button btn1,btn2;
private TextView tv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 获取ViewModel实例
myViewModel = ViewModelProviders.of(this).get(MyViewModel.class);
btn1 = findViewById(R.id.btn1);
btn2 = findViewById(R.id.btn2);
tv = findViewById(R.id.tv);
// 屏幕切换等生命周期重置需重新手动设置一下最新值
tv.setText(myViewModel.getNumber()+"");
btn1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
myViewModel.doSet(1);
tv.setText(myViewModel.getNumber()+"");
}
});
btn2.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
myViewModel.doSet(-1);
tv.setText(myViewModel.getNumber());
}
});
}
}
注意:
1,ViewModelProviders类的使用需要在app的build.gradle文件中添加如下依赖,否则会找不到:
implementation 'androidx.lifecycle:lifecycle-extensions:2.1.0'
2,ViewModel的功能之一是当activity重建时里面的数据不会丢失,因此运行程序,我们横竖屏切换使activity重新创建,结果发现切换之后TextView的值变成了我们在布局中定义的值,这时因为虽然ViewModel中的数据被保存了下来,但是在activity重建时并没有引用viewmodel中的最新值,因此我们在oncreate中需要手动设置一下最新值。
如果不使用ViewModel,我们一般的做法都是在activity中创建全局的成员变量int=0,然后在按钮的点击事件中直接操作该数据,并设置给TextView,而且在activity重建之后数据还会丢失,还需要我们进行额外的数据保存和恢复操作。
通过上面简单的实例我们可以明显感受到使用了ViewModel之后,两个优点:
1,将数据管理从activity中分离,使模块更清晰,进一步解耦
2,同时也不用我们在额外的处理数据的保存和恢复,同样当一个activity中包含多个fragment时,也可以很方便的实现数据在各个fragment之间的共享。
通过上面在activity中使用ViewModel的案例中我们发现,虽然与ui相关的数据我们进行了集中管理,但是每次更新ui的时候依然还是需要我们手动去设置TextView的值,假如我们在设置TextView的值时,TextView被销毁了,那么还会报空指针异常,所以必要时我们还必须进行判空操作,这样如果业务逻辑一复杂,就很容易出现内存泄漏的风险,但是如果我们使用LiveData就很容易避免这些问题,我们可以在ViewModel中创建LiveData实例,来实时更新UI,并且不用担心会出现空指针的问题,关于LiveData以及两者的结合使用可见下一篇。
在Activity中的多个Fragment需要通信这是很常见的,这些Fragment可以使用它们的Activity范围域共享一个ViewModel来处理这种通信,示例代码如下:
public class Fragment01 extends Fragment {
private MyViewModel model;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
model = ViewModelProviders.of(getActivity()).get(MyViewModel.class);
btn1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
model.doSet(666);
}
});
}
}
public class Fragment02 extends Fragment {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
MyViewModel model = ViewModelProviders.of(getActivity()).get(MyViewModel.class);
tv.setText(model.getNumber()+"");
}
}
注意:上面两个Fragment通过使用getActivity()方法来获取ViewModelProviders,这意味着它们都将接收相同的SharedViewModel实例,该实例的作用域为Activity。如果传给方法ViewModelProviders.of()的对象不同时,最终得到的就不是同一个ViewModel对象。