LiveData 一般是和 ViewModel 结合起来使用的,比如计数器功能,在单线程模式下确实可以正常工作,但如果在 ViewModel 的内部开启了线程去执行一些耗时逻辑,那么在点击按钮后就我们想立即去获取最新的数据,但是得到的肯定还是之前的旧数据。
**原本我们一直使用的都是在 Activity 中手动的获取 ViewModel 中的数据这种交互方式,但是 ViewModel 却无法将数据的变化主动地通知给 Activity。**那么怎么实现主动通知呢?或许你会说将 Activity 的实例传给 ViewModel,这样 ViewModel 不就能主动通知 Activity 了吗?很可惜,这是错误的。因为 ViewModel 的生命周期是长于 Activity,如果这样做了,那么可能因为 Activity 无法释放而造成内存泄漏。
LiveData 可以轻松地解决这个问题。LiveData 可以包含任何类型的数据,并在数据发生变化的时候通知给观察者。也就是说,我们可以将数据使用 LiveData 来包装,然后在 Activity 中去观察它,就可以将数据变化通知给 Activity。
使用示例:这里和 ViewModel 结合使用
public class MyViewModel extends ViewModel {
public MyViewModel(int countReserved) {
this._counter.setValue(countReserved);
}
// LiveData 是不可变的
// 我们给 _counter 加上 private 修饰符,这样 _counter 外部就不可见了。
// 然后我们定义不可变的 counter 变量,并让它的 getValue() 方法返回 _counter 中的数据。
// 这样,当外部调用 counter 变量时,其实获取到的是 _counter 的值,
// 但是无法给 _counter 设置数据,从而保证了ViewModel 的数据封装性
public LiveData<Integer> counter = new LiveData<Integer>() {
@Override
public Integer getValue() {
return _counter.getValue();
}
};
// MutableLiveData 是一种可变的 LiveData
private MutableLiveData<Integer> _counter = new MutableLiveData<Integer>();
public void plus() {
int value = counter.getValue() == null ? 0 : counter.getValue()+1;
_counter.setValue(value);
}
public void clear() {
_counter.setValue(0);
}
}
主要有三种读写数据的方法,分别是 getValue()、setValue() 和 postValue() 方法。setValue() 只能在主线程中使用,postValue() 可以用于非主线程中使用。
viewModel1 = new ViewModelProvider(this).get(MyViewModel.class);
// 当 counter 中包含的数据发生变化时就会回调到这里
viewModel1.counter.observe(this, new Observer<Integer>() {
@Override
public void onChanged(Integer integer) {
String text = viewModel1.counter.getValue().toString();
infoText.setText(text);
}
});
...
map() 方法的作用:将实际包含数据的 LiveData 和 仅用于观察数据的 LiveData 进行转换。
举个栗子:
比如有一个 User 类,User 中包含姓名和年龄,如果观察者只需要姓名而不关心年龄,那么这个时候还将整个 User 类型的 LiveData 暴露给外部就显得不那么合适了。
public class User {
String firstname;
String lastname;
int age;
public User(String firstname, String lastname, int age) {
this.firstname = firstname;
this.lastname = lastname;
this.age = age;
}
}
public class MyViewModel extends ViewModel {
public MyViewModel(User user) {
this.userLiveData.setValue(user);
}
private final MutableLiveData<User> userLiveData = new MutableLiveData<>();
// 调用 Transformations 的 map() 方法来对LiveData 的数据类型进行转换,
// map接收两个参数,第一个参数是原始的 LiveData 对象;
// 第二个参数是一个转换函数,我们在这里编写具体的转换逻辑即可。
public final LiveData<String> username = Transformations.
map(userLiveData, new Function<User, String>() {
@Override
public String apply(User user) {
return user.firstname+", "+user.lastname;
}
});
}
当 userLiveData 的数据发生变化时,map() 方法会监听到变化并执行转换函数中的逻辑,然后再将转换之后的数据通知给userLiveData 的观察者。
switchMap()
前面我们所学的所有内容都有一个前提:LiveData 对象的实例都是在 ViewModel 中创建的。然而在实际的项目中,不可能一直是这种理想情况,很有可能 ViewModel 中的某个 LiveData 对象是调用另外的方法获取的,那么我们就可以借助 switchMap() 方法将这个LiveData 对象转换成另一个可观察的 LiveData 对象。
举个栗子:
这里我们在 Repository 类中添加了一个 getUser() 方法,这个方法接收一个 userId 参数,每次将传入的 userId 当作用户姓名来创建一个新的 User 对象即可。
// 单例类
public class Repository {
private static Repository repository;
public static Repository getRepository() {
if (repository == null) {
repository = new Repository();
}
return repository;
}
public LiveData<User> getUser(String userId) {
MutableLiveData<User> liveData = new MutableLiveData<>();
liveData.setValue(new User(userId, userId, 0));
return liveData;
}
}
// 这里我们定义了一个新的 userIdLiveData 对象,用来观察 userId 的数据变化,
// 然后调用了 Transformations 的 switchMap() 方法,用来对另一个可观察的 LiveData 对象进行转换。
public class MyViewModel extends ViewModel {
private final MutableLiveData<String> userIdLiveData =
new MutableLiveData<>();
public final LiveData<User> user = Transformations.switchMap(userIdLiveData,
new Function<String, LiveData<User>>() {
@Override
public LiveData<User> apply(String userId) {
return Repository.getRepository().getUser(userId);
}
});
public void getUser(String userId) {
userIdLiveData.setValue(userId);
}
}
我么再来梳理一遍它的整体工作流程:首先,当外部调用 MyViewModel 的 getUser() 方法来获取用户数据时,并不会发起任何请求和函数调用,只会将传入的 userId 值设置到 userIdLiveData 当中。一旦 userIdLiveData 的数据发生变化,那么观察 userIdLiveData 的 switchMap() 方法就会执行,并且调用我们编写的转换函数。然后在转换函数中调用 Repository.getUser() 方法获取真正的用户数据。同时 switchMap() 方法会将 Repository.getUser() 方法返回的 LiveData 对象转换成一个可观察的 LiveData 对象,对于 Activity 而言,只要去观察这个 LiveData 对象即可。