安卓Jetpack架构组件(二):ViewModel+LiveData的使用

前言

上一篇我们介绍了单独的ViewModel组件的使用实例,Android文档中建议我们LiveData配合ViewModel使用,那么LiveData到底是什么,两者怎么结合使用呢?

LiveData是一个可观察的数据持有者类,具有如下优点:
1,LiveData作为被观察者,数据可以被观察者订阅,当数据有变化时会通知观察者(UI)。
2,减少内存泄漏,因为LiveData能够感知组件的生命周期,页面销毁时他们会自动被移除,不会导致内存溢出,且当我们执行异步的网络请求时,请求到的结果需要更新ui,但是此时activity已经被销毁了,就可以避免nullpointexception异常的产生。
3,组件和数据相关的内容能实时更新,底层数据改变时及时通知ui。
4,数据共享,通过继承LiveData类,然后将该类定义成单例模式,在该类封装监听一些系统属性变化,然后通知LiveData的观察者

LiveData的使用

1,ViewModel改造:在ViewModel中创建LiveData的实例,如下:

public class MyViewModel extends ViewModel {

    private MutableLiveData<Integer> number;

    /* MutableLiveData中地数据无法直接赋值,需通过setValue或getaValue赋值或取值 */
    /* 获取thumbup的值 */
    public MutableLiveData<Integer> getNumber() {
        if (number==null){
            number = new MutableLiveData<>();
            number.setValue(0);
        }
        return number;
    }


    public void doSet(int n){
        number.setValue(number.getValue()+n);
    }

    @Override
    protected void onCleared() {
        super.onCleared();
        // 当activity或者fragment销毁时会调用该方法
    }
}

2,对应的activity类如下:

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);

        btn1 = findViewById(R.id.btn1);
        btn2 = findViewById(R.id.btn2);
        tv = findViewById(R.id.tv);

        // 获取ViewModel实例
        myViewModel = ViewModelProviders.of(this).get(MyViewModel.class);
        // 设置ViewModel中的变量number自己观察自己,当自己发生改变时,更新TextView的显示
        // 同理ViewModel中也可以定义其他的成员变量(变量类型均为MutableLiveData,泛型也可以为集合),来动态更新不同的View
        // 当视图销毁会自动取消观察订阅,和移除组件,避免了内存泄漏和空指针异常
        myViewModel.getNumber().observe(this, new Observer<Integer>() {
            @Override
            public void onChanged(Integer integer) {
                // 当数据发生改变时会回调该方法
                tv.setText(myViewModel.getNumber()+"");
            }
        });


        btn1.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                myViewModel.doSet(1);
            }
        });

        btn2.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                myViewModel.doSet(-1);
            }
        });
    }
}

总结:可以看到结合LiveData使用之后,我们不再需要每次数据发生变化都频繁的手动在activity中去设置TextView的值,并且也无需担心内存泄漏和空指针等问题。

进阶(ViewModel+LiveData+DataBinding)

观察上面的例子我们发现,activity中存在着大量的控件初始化以及设置点击事件的方法,我们可以借助databinding来进一步简化activity中的逻辑,如下:
1,在app的build.gradle文件中的defaultConfig标签中添加如下:

defaultConfig {
        ...
        versionName "1.0"
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
        dataBinding {
            enabled true
        }
    }

2,编译成功后,切换到布局文件,点击左上角的小灯泡处的下拉选项,选择Convert to data binding layout,如图:
安卓Jetpack架构组件(二):ViewModel+LiveData的使用_第1张图片
会自动生成部分代码,如下:

<?xml version="1.0" encoding="utf-8"?>
<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>

    </data>

    <LinearLayout
        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>
</layout>

可以看到在我们原有布局的最外层添加了一对layout标签,并且在其中多了一对data标签,
3,当我们将布局进行上述的转换之后,系统会自动为我们生成一个ActivityMainBinding类,类名为layout的文件名+Binding,对应的activity代码如下:

public class MainActivity extends AppCompatActivity {

    private MyViewModel myViewModel;

    ActivityMainBinding activityMainBinding;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        
//        setContentView(R.layout.activity_main);
        activityMainBinding = DataBindingUtil.setContentView(this,R.layout.activity_main);

        // 获取ViewModel实例
        myViewModel = ViewModelProviders.of(this).get(MyViewModel.class);
        // 设置ViewModel中的变量number自己观察自己,当自己发生改变时,更新TextView的显示
        // 同理ViewModel中也可以定义其他的成员变量(变量类型均为MutableLiveData,泛型也可以为集合),来动态更新不同的View
        // 当视图销毁会自动取消观察订阅
        myViewModel.getNumber().observe(this, new Observer<Integer>() {
            @Override
            public void onChanged(Integer integer) {
                // 当数据发生改变时会回调该方法
                activityMainBinding.tv.setText(myViewModel.getNumber()+"");
            }
        });


        activityMainBinding.btn1.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                myViewModel.doSet(1);
            }
        });

        activityMainBinding.btn2.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                myViewModel.doSet(-1);
            }
        });
    }
}

可以看到我们需要注释掉activity中原有的setContentView方法,使用DataBindingUtil.setContentView(this,R.layout.activity_main)的形式来设置并获得ActivityMainBinding的类实例,这样布局文件中的所有控件我们都可以通过ActivityMainBinding的类实例名.对应控件的id的形式来访问了,就不再需要声明控件并findviewbuid了。

如上通过自动生成的ActivityMainBinding类实例就实现了布局文件中控件到activity中的动态绑定,那如果需要将activity中的数据反向绑定到布局文件中的指定控件上,而不是使用传统的在点击事件中的做法那样,我们就需要使用到布局文件中的data标签,在data标签中我们可以使用如下的方式来创建变量:

<data>
     <variable
         name="data"
         type="com.example.viewmodeldemo.MyViewModel" />
</data>

其中对应的data为变量名,type为变量类型,接下来我们就可以在布局文件中写源代码了,需要使用到@{ },其中的大括号里面就可以写java源代码,如下:

<?xml version="1.0" encoding="utf-8"?>
<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.viewmodeldemo.MyViewModel" />
    </data>

    <LinearLayout
        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="@{String.valueOf(data.number)}"
            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:onClick="@{()->data.doSet(1)}"
            android:text="+1" />

        <Button
            android:id="@+id/btn2"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:onClick="@{()->data.doSet(-1)}"
            android:text="-1" />

    </LinearLayout>
</layout>

可以看到我们使用@{ }的形式通过代码进行了值的设置,同时也可以绑定点击事件,只是大括号里面的写法格式不同而已,关于databinding的语法格式可自行查阅资料。

这样一来,在activity中的代码就大大简化了,现在的activity代码如下:

public class MainActivity extends AppCompatActivity {

    private MyViewModel myViewModel;

    ActivityMainBinding activityMainBinding;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

//        setContentView(R.layout.activity_main);
        activityMainBinding = DataBindingUtil.setContentView(this,R.layout.activity_main);

        // 获取ViewModel实例
        myViewModel = ViewModelProviders.of(this).get(MyViewModel.class);
        // 为应布局文件中的变量data进行初始化
        activityMainBinding.setData(myViewModel);
        // 监听自身数据的变化
        activityMainBinding.setLifecycleOwner(this);
    }
}

可以看到activity中代码相比最开始的案例大大简化了,使模块化更清晰。

你可能感兴趣的:(Android)