JetPack-(5)-LiveData 可被观察的数据容器类

1.LiveData介绍

LiveData是一个可被观察的数据容器类,它可以包含任何类型的数据。具体来说,可以将LiveData理解为一个数据的容器,它将数据包装起来,使数据成为观察者,当该数据发生变化时,观察者能够获得通知。与常规的可观察类不同,LiveData可以感知(如Activity,Fragment或Service)的生命周期。

简单来说,LiveData具有如下优势

  1. LiveData遵循观察者模式。当生命周期状态发生变化时,LiveData会通知Observer对象,可以在这些Observer对象中更新界面。

  2. 不会内存泄漏

  3. 如果观察者的生命周期处于非活跃状态(如返回栈中的Activity),则它不会接收任何LiveData事件,但是,当非活跃状态变成活跃状态时会立刻接收最新的数据(后台的Activity返回前台时)

  4. 当config导致Activity/Fragment重建时,不需要再手动的管理数据的存储与恢复。

2.LiveData和ViewModel的关系

ViewModel用于存放页面所需要的各种数据,对于页面来说,它并不关心ViewModel中的业务逻辑,它只关心需要展示的数据是什么,并且希望在数据发生变化时,能够及时得到通知并作出更新。

LiveData的作用就是,在ViewModel中的数据发生变化时通知页面,用于包装ViewModel中哪些需要被外界观察的数据。

3.LiveData使用方法

首先,在ViewModel视图模型中定义LiveData数据,如 MutableLiveData

在该类中提供了postValue和setValue两个函数

  • setValue() 只能在主线程中调用给LiveData设置数据
  • postValue()用于在非主线程中给LiveData设置数据

然后,在Activity组件中,调用LiveData参数的observer方法,添加数据变化监听器 androidx.lifecycle.Observer,一旦LiveData数据发生了改变,就会回调Observer监听器中的onChanged函数

3.ViewModel+LiveData简单使用

3.1 在ViewModel中

首先在ViewModel中定义LiveData数据,由于LiveData是一个抽象类,不能直接使用它,所以我们通常使用的都是它的直接子类MutableLiveData,然后定义了一个Integer类型的值,当该值发生改变时,会触发LiveData设置的Observer监听器。

3.2 在Activity中

在Activity系统组件中,绑定ViewModel,从ViewModel中获取LiveData显示到UI界面中,并为该LiveData设置Observer监听器,监听LiveData的数据变化 启动Timer定时器,修改ViewModel中的LiveData数据,在LiveData数据发生改变时,会自动回调Observer监听器的onChanged方法

scss 复制代码
public class MainActivity extends AppCompatActivity {

    private MyViewModel myViewModel;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        TextView textView = findViewById(R.id.textview);

        myViewModel = new ViewModelProvider(this, ViewModelProvider.AndroidViewModelFactory.getInstance(getApplication()))
                .get(MyViewModel.class);
        //将ViewModel中的数据设置到视图View组件中
        textView.setText(String.valueOf(myViewModel.getSecond().getValue()));

        //设置LiveData监听
        myViewModel.getSecond().observe(this, new Observer<Integer>() {
            @Override
            public void onChanged(Integer integer) {
                //将ViewModel中的数据设置到视图View组件中
                textView.setText(String.valueOf(integer));
            }
        });
        startTimer();
    }

    public void startTimer() {
        new Timer().schedule(new TimerTask() {
            @Override
            public void run() {
                //获取ViewModel中的数据
                Integer value = myViewModel.getSecond().getValue();
                //将ViewModel中的数据自增1
                myViewModel.getSecond().postValue(value + 1);
            }
        }, 1000, 1000);
    }
}

3.3 运行效果:

应用启动后,在界面中启动定时器,对ViewModel中的LiveData数据进行累加,LiveData设置了Observer监听,数据改变时回调Observer的onChanged方法更新UI显示。就算切换屏幕反向,也不会影响数据累加显示

竖屏状态下

切换横屏数据依然存在

4.ViewModel+LiveData+Fragment使用

在Activity系统组件中设置两个Fragment,两个Fragment之间通过ViewModel+LiveData进行通信

在其中一个Fragment中设置SeekBar拖动条,将数值设置到另外一个Fragment中的TextView中显示

4.1 ViewModel + LiveData代码

自定义ViewModel子类继承ViewModel,在ViewModel中,定义LiveData类型的数据,此处选择使用MutableLiveData< Integer >数据类型,当该值发生变化的时候,会触发LiveData设置的Observer监听器

4.2 Activity组件代码

在该Activity组件中,维护了两个Fragment,两个Fragment之间借助ViewModel+LiveData进行通信

布局文件: 在Activity中设置了两个Fragment,他们之间借助ViewModel+LiveData进行通信

ini 复制代码
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.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=".HomeActivity">

   <androidx.constraintlayout.widget.Guideline
       android:id="@+id/guideline"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:orientation="horizontal"
       app:layout_constraintGuide_percent="0.5" />

   <androidx.fragment.app.FragmentContainerView
       android:id="@+id/fragmentContainerView1"
       android:name="com.wzj.livedata.Fragment1"
       android:layout_width="0dp"
       android:layout_height="0dp"
       app:layout_constraintBottom_toTopOf="@+id/guideline"
       app:layout_constraintEnd_toEndOf="parent"
       app:layout_constraintStart_toStartOf="parent"
       app:layout_constraintTop_toTopOf="parent" />

   <androidx.fragment.app.FragmentContainerView
       android:id="@+id/fragmentContainerView2"
       android:name="com.wzj.livedata.Fragment2"
       android:layout_width="0dp"
       android:layout_height="0dp"
       app:layout_constraintBottom_toBottomOf="parent"
       app:layout_constraintEnd_toEndOf="parent"
       app:layout_constraintStart_toStartOf="parent"
       app:layout_constraintTop_toTopOf="@+id/guideline" />

</androidx.constraintlayout.widget.ConstraintLayout>

4.3 Fragment代码

第一个Fragment布局

Fragment中创建了一个SeekBar拖动条组件

第一个Fragment代码

先将ViewModel中的LiveData数据中的进度值给SeekBar,目的是为了在屏幕旋转的时候,可以随时恢复数据

然后在SeekBar的拖动数据中,修改ViewModel中的LiveData数据 当数据修改时,对应的Fragment2中的TextView会刷新显示新的数据

java 复制代码
public class Fragment1 extends Fragment {


    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_1, container, false);

        //获取拖动条
        SeekBar seekBar = view.findViewById(R.id.seekBar);

        //获取ViewModel
        HomeViewModel homeViewModel = new ViewModelProvider(requireActivity(),  ViewModelProvider.AndroidViewModelFactory.getInstance(requireActivity().getApplication()))
                .get(HomeViewModel.class);

        seekBar.setProgress(homeViewModel.getProgress().getValue());

        //设置进度条拖动事件
        seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
            @Override
            public void onProgressChanged(SeekBar seekBar, int i, boolean b) {
                homeViewModel.getProgress().setValue(i);
            }

            @Override
            public void onStartTrackingTouch(SeekBar seekBar) {

            }

            @Override
            public void onStopTrackingTouch(SeekBar seekBar) {

            }
        });

        return view;
    }
}

第二个Fragment布局

Fragment2中只有一个TextView

第二个Fragment代码

在 Fragment2 中 , 只放了一个 TextView 组件 , 该组件显示的是 ViewModel 中的 LiveData 数据 , 当该 LiveData 数据发生改变时 , 对应 TextView 显示也随之更新 ;

scala 复制代码
public class Fragment2 extends Fragment {

   @Override
   public View onCreateView(LayoutInflater inflater, ViewGroup container,
                            Bundle savedInstanceState) {
       // Inflate the layout for this fragment
       View view = inflater.inflate(R.layout.fragment_2, container, false);

       //获取文本
       TextView textView = view.findViewById(R.id.textViews);

       //获取ViewModel
       HomeViewModel homeViewModel = new ViewModelProvider(requireActivity(), ViewModelProvider.AndroidViewModelFactory.getInstance(requireActivity().getApplication())).get(HomeViewModel.class);

       //设置数据
       textView.setText(String.valueOf(homeViewModel.getProgress().getValue()));

       homeViewModel.getProgress().observe(requireActivity(), new Observer<Integer>() {
           @Override
           public void onChanged(Integer integer) {
               textView.setText(String.valueOf(integer));
           }
       });

       return view;
   }
}

5.Map和switchMap

5.1 Map

map函数主要的功能就是根据原LiveData,对其原LiveData的值进行改变然后生成一个新的LiveData。基于原LiveData。新的LiveData的值必须基于旧的LiveData中的值

Transformations.map函数的作用是创建一个新的LiveData对象,这个新的LiveData对象的值是原始LiveData对象的值经过函数处理后的结果。这个函数接收两个参数,一个LiveData对象和一个函数.这个函数将被应用到LiveData对象的每一个值上

举例说明:

在ViewModel中使用 Transformations.map将原LiveData进行更改

首先我们定义一个实体类:

这就是一个普通的实体类,没什么好说的

typescript 复制代码
public class User {
    private String firstName;
    private String lastName;
    private int age;

    public User(String firstName, String lastName, int age) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.age = age;
    }

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

然后定义ViewModel

typescript 复制代码
public class UserViewModel extends ViewModel {
   private MutableLiveData<User> userLiveData;
   private LiveData<String> userName;
   private int countReserved;

   public UserViewModel() {
       this.countReserved = countReserved;
       this.userLiveData = new MutableLiveData<>();
       //使用转换函数map,创建一个新的LiveData对象
       this.userName = Transformations.map(userLiveData, user -> user.getFirstName() + " " + user.getLastName());
   }

   public MutableLiveData<User> getUserLiveData() {
       return userLiveData;
   }

   public void setUserLiveData(MutableLiveData<User> userLiveData) {
       this.userLiveData = userLiveData;
   }

   public LiveData<String> getUserName() {
       return userName;
   }

   public void setUserName(LiveData<String> userName) {
       this.userName = userName;
   }

   public int getCountReserved() {
       return countReserved;
   }

   public void setCountReserved(int countReserved) {
       this.countReserved = countReserved;
   }
}

注意看第11行的代码,

ini 复制代码
this.userName = Transformations.map(userLiveData, user -> user.getFirstName() + " " + user.getLastName());

这段代码的作用是创建一个新的LiveData对象userName,它的值是userLiveData中User对象的firstName和lastName,每当userLiveData的值发生变化时,userName的值也会相应的更新

在Activity中

java 复制代码
public class UserActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_user);
        TextView tv_user = findViewById(R.id.tv_user);
        Button btn_user = findViewById(R.id.btn_user);

        UserViewModel userViewModel = new ViewModelProvider(this).get(UserViewModel.class);

        btn_user.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                User user = new User("名字1", "名字2", 888);
                //给ViewModel设置数据
                Toast.makeText(UserActivity.this, user.getFirstName() +" "+ user.getLastName() +" "+ user.getAge(), Toast.LENGTH_SHORT).show();
                userViewModel.getUserLiveData().postValue(user);
            }
        });

        userViewModel.getUserName().observe(this, new Observer<String>() {
            @Override
            public void onChanged(String s) {
                tv_user.setText(userViewModel.getUserName().getValue());
            }
        });

    }
}

在Activity中,我们给User设置的值是两个名字加年龄参数,然后在显示的时候,我们用的是UserName的值 而不用原本的UserLiveData,所以打印出来的时候,应该只有两个名字 而没有了年龄这个参数

5.2 switchMap

switchMap是根据传入的LiveData的值,然后判断这个值,然后再去切换或者构建新的LiveData。手动生成新的LiveData

6.总结

LiveData之所以能够成为Activity与ViewModel之间通信的桥梁,并且还不会有内存泄漏的风险,靠的就是Lifecycles组件。LiveData在内部使用了Lifecycles组件来自我感知生命周期的变化,从而可以在Activity销毁的时候及时释放引用,避免产生内存泄漏的问题。

另外,由于要减少性能消耗,当Activity处于不可见状态的时候(比如手机息屏,或者被其他Activity遮挡),如果LiveData中的数据发生了变化,是不会通知给观察者的。只有当Activity重新恢复可见状态时,才会将数据通知给观察者。而LiveData之所以能够实现这些细节的优化,依靠的还是Lifecycles组件。

还有一个小细节,如果在Activity处于不可见状态的时候,LiveData发生了多次数据变化,当Activity恢复可见状态时,只有最新的那份数据状态才会通知给观察者,前面的数据在这种情况下相当于已经过期了,会被直接丢弃。

相关推荐
喵叔哟30 分钟前
重构代码之取消临时字段
java·前端·重构
还是大剑师兰特1 小时前
D3的竞品有哪些,D3的优势,D3和echarts的对比
前端·javascript·echarts
王解1 小时前
【深度解析】CSS工程化全攻略(1)
前端·css
一只小白菜~1 小时前
web浏览器环境下使用window.open()打开PDF文件不是预览,而是下载文件?
前端·javascript·pdf·windowopen预览pdf
方才coding1 小时前
1小时构建Vue3知识体系之vue的生命周期函数
前端·javascript·vue.js
阿征学IT1 小时前
vue过滤器初步使用
前端·javascript·vue.js
王哲晓1 小时前
第四十五章 Vue之Vuex模块化创建(module)
前端·javascript·vue.js
丶21361 小时前
【WEB】深入理解 CORS(跨域资源共享):原理、配置与常见问题
前端·架构·web
发现你走远了1 小时前
『VUE』25. 组件事件与v-model(详细图文注释)
前端·javascript·vue.js
Mr.咕咕2 小时前
Django 搭建数据管理web——商品管理
前端·python·django