安卓架构组件(一):ViewModel的使用

前言

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,具体请看下面的示例。

Activity中使用实例

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之间的共享。

进阶(结合LiveData使用)

通过上面在activity中使用ViewModel的案例中我们发现,虽然与ui相关的数据我们进行了集中管理,但是每次更新ui的时候依然还是需要我们手动去设置TextView的值,假如我们在设置TextView的值时,TextView被销毁了,那么还会报空指针异常,所以必要时我们还必须进行判空操作,这样如果业务逻辑一复杂,就很容易出现内存泄漏的风险,但是如果我们使用LiveData就很容易避免这些问题,我们可以在ViewModel中创建LiveData实例,来实时更新UI,并且不用担心会出现空指针的问题,关于LiveData以及两者的结合使用可见下一篇。

在多个Fragment间共享数据

在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对象。

你可能感兴趣的:(Android)