1.LiveData介绍
LiveData是一个可被观察的数据容器类,它可以包含任何类型的数据。具体来说,可以将LiveData理解为一个数据的容器,它将数据包装起来,使数据成为观察者,当该数据发生变化时,观察者能够获得通知。与常规的可观察类不同,LiveData可以感知(如Activity,Fragment或Service)的生命周期。
简单来说,LiveData具有如下优势
-
LiveData遵循观察者模式。当生命周期状态发生变化时,LiveData会通知Observer对象,可以在这些Observer对象中更新界面。
-
不会内存泄漏
-
如果观察者的生命周期处于非活跃状态(如返回栈中的Activity),则它不会接收任何LiveData事件,但是,当非活跃状态变成活跃状态时会立刻接收最新的数据(后台的Activity返回前台时)
-
当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恢复可见状态时,只有最新的那份数据状态才会通知给观察者,前面的数据在这种情况下相当于已经过期了,会被直接丢弃。