---
title: 6.LiveData
date: 2019-06-11
categories:
- Kotlin
tags:
- Kotlin
description:
---
两种转化LiveData对象的方法:map()和switchMap()
public static LiveData<Y> map (LiveData<X> source,
Function<X, Y> mapFunction)
此函数接收两个参数,第一个参数是用于转换的LiveData原始对象,第二个参数是转换函数。
举例如下: 假设现在有一个User类,属性包括firstName,lastName,age。
data class User(var firstName: String, var lastName: String, var age: Int)
在ViewModel中创建一个User类型的LiveData对象。
val userLiveData = MutableLiveData<User>()
但是不需要将用户的age展示出来,此时就可以通过Transformations.map()创建一个新的LiveData对象。
val userName:LiveData<String> = Transformations.map(userLiveData) {
user->
"${user.firstName} ${user.lastName}"//封装数据
}
然后就可以直接监听这个新的LiveData对象。
viewModel = ViewModelProvider(this).get(MapTestViewModel::class.java)
viewModel.userName.observe(this, Observer {
string->
text.text = string
})
Map()函数可以用于对数据的封装
public static LiveData<Y> switchMap (LiveData<X> source,
Function<X, LiveData<Y>> switchMapFunction)
switchMap()函数同样接收两个参数,第一个参数是一个LiveData对象,当此LivaData对象变化时就调用转换函数生成一新的LiveData对象,第二个参数就是转化函数。 switchMap()函数可用于LiveData对象不是直接在ViewModel中创建,而是调用其他方法创建的。
private val queryLiveData = MutableLiveData<String>() //直接创建
fun queryPlaces(name: String):LiveData<Result<List<Place>>>{
return Repository.searchPlaces(name)
}//利用其他方法创建
举例如下:
class PlaceViewModel: ViewModel() {
fun queryPlaces(name: String): LiveData<Result<List<Place>>>{
return Repository.searchPlaces(name)
}
}
在ViewModel中有一个queryPlaces()方法,该方法产生一个LiveData对象,现在要监听这个对象,代码如下
val data = viewModel.queryPlaces(text) //每次调用该方法都会产生新的LiveData对象,无法监听原来的LiveData对象
data.observe(viewLifecycleOwner, Observer{
})
每次调用queryPlaces()方法生成的LiveData对象都是一个新的对象,无法监听原来的LiveData对象。此时就可以用swithMap()方法创建一个可观察的,不会改变的LiveData对象。 但是需要在ViewModel中多创建一个LiveData对象,用于监听数据变化。当LiveData的值变化时就会触发转化函数
class PlaceViewModel: ViewModel() {
private val queryLiveData = MutableLiveData<String>()
fun queryPlaces(name: String){
queryLiveData.value = name
}
val placesLiveData = Transformations.switchMap(queryLiveData){
//当queryLiveData的值变化时就会触发转化函数
name->
Repository.searchPlaces(name)
}
}
观察placesLiveData对象
viewModel.placesLiveData.observe(viewLifecycleOwner, Observer {
//此时的LiveData对象不会改变
})
此时观察的LiveData对象不会改变,也就不用解除绑定,重新绑定了。
switchMap()函数可用于LiveData对象不是直接在ViewModel中创建,而是调用其他方法创建的。
何为粘性事件? 即发射的事件如果早于注册,那么注册之后依然可以接收到的事件称为粘性事件. 具体代码中指的是,先setValue/postValue,后调用observe(),如果成功收到了回调,即为粘性事件。
数据倒灌:“数据倒灌”一词最先由大佬KunMinX提出,虽然给出了示例,但并没有给出文字定义。我的理解是,先setValue/postValue,后调用observe(new Obs()),至此收到了回调。然后再第二次调用observe(new anotherObs()),如果还能收到第一次的回调,则为“数据倒灌”。
当 setValue/postValue 的时候, 会走 considerNotify, 考虑是否需要回调观察者的回调方法:
private void considerNotify(ObserverWrapper observer) {
if (!observer.mActive) {
return;
}
// Check latest state b4 dispatch. Maybe it changed state but we didn't get the event yet.
//
// we still first check observer.active to keep it as the entrance for events. So even if
// the observer moved to an active state, if we've not received that event, we better not
// notify for a more predictable notification order.
if (!observer.shouldBeActive()) {
observer.activeStateChanged(false);
return;
}
if (observer.mLastVersion >= mVersion) {
return;
}
observer.mLastVersion = mVersion;
//noinspection unchecked
observer.mObserver.onChanged((T) mData);
}
最终会判断mLastVersion是否比mVersion大,如果小于mVersion,那么会调用onChanged方法,即我们在MainActivity里面注册的事件。那么mLastVersion和mVersion是什么,接着往下看:
private int mVersion = START_VERSION;
protected void setValue(T value) {
assertMainThread("setValue");
mVersion++;
mData = value;
dispatchingValue(null);
}
我们发现mVersion初始值是-1,这个mVersion是属于LiveData的,然后只在setValue(postValue最终也会调用setValue)的时候会自增1,那么mLastVersion呢?
int mLastVersion = START_VERSION;
发现mLastVersion也是初始值为-1,而这个mLastVersion是属于ObserverWrapper的,而赋值的地方只有在比较完才会赋值.
那么仔细回想下,我们其实在onCreate那里注册的观察者信息其实有过一次setValue的操作了,又因为我们其实用的是同一个LiveData,所以mVersion最后是会自增1的,又因为ObserverWrapper在每次注册的时候都会重新new,所以mLastVersion每次都是-1开始。 那么真相大白了,只要之前有发射过一次数据,那么后面注册的观察者都会接收到之前发射过的数据,而且看样子这个Version值不可以轻易改变,也就是说谷歌不提供API让我们取消掉粘性事件.
-
不要多次注册
-
反射修改 version 的值
只需要在调用observer之前的某个节点处改,变使其mLastVersion = mVersion即可。
private void hook(@NonNull Observer<T> observer) throws Exception {
//get wrapper's version
Class<LiveData> classLiveData = LiveData.class;
Field fieldObservers = classLiveData.getDeclaredField("mObservers");
fieldObservers.setAccessible(true);
Object objectObservers = fieldObservers.get(this);
Class<?> classObservers = objectObservers.getClass();
Method methodGet = classObservers.getDeclaredMethod("get", Object.class);
methodGet.setAccessible(true);
Object objectWrapperEntry = methodGet.invoke(objectObservers, observer);
Object objectWrapper = null;
if (objectWrapperEntry instanceof Map.Entry) {
objectWrapper = ((Map.Entry) objectWrapperEntry).getValue();
}
if (objectWrapper == null) {
throw new NullPointerException("Wrapper can not be bull!");
}
Class<?> classObserverWrapper = objectWrapper.getClass().getSuperclass();
Field fieldLastVersion = classObserverWrapper.getDeclaredField("mLastVersion");
fieldLastVersion.setAccessible(true);
//get livedata's version
Field fieldVersion = classLiveData.getDeclaredField("mVersion");
fieldVersion.setAccessible(true);
Object objectVersion = fieldVersion.get(this);
//set wrapper's version
fieldLastVersion.set(objectWrapper, objectVersion);
}
}
然后重写继承重写LiveData,将这个hook方法放在observe方法中。
这样一来,使用该自定义的LiveData时就会发现,先setValue,后observe的做法已经行不通了,这就是所谓的非粘性。
- UnPeekLiveData
// 自定义了 LiveData
public class ProtectedUnPeekLiveData<T> extends LiveData<T> {
protected boolean isAllowNullValue;
// 多个观察者 key 是观察者的 id, value 是能否触发观察者的onChanged方法
private final HashMap<Integer, Boolean> observers = new HashMap<>();
// 注册的时候, 调用这个方法
// liveData.observeInActivity();
public void observeInActivity(@NonNull AppCompatActivity activity, @NonNull Observer<? super T> observer) {
LifecycleOwner owner = activity;
Integer storeId = System.identityHashCode(observer);//源码这里是activity.getViewModelStore(),是为了保证同一个ViewModel环境下"唯一可信源"
observe(storeId, owner, observer);
}
private void observe(@NonNull Integer storeId,
@NonNull LifecycleOwner owner,
@NonNull Observer<? super T> observer) {
// 默认是不可以触发的. true 是不可以触发. 也就是阻止进入
// 这样的话, 先 setValue, 再 observe, 也是收不到回调的, 因为 observe的时候把 boolean 值设置成了 true. 下面 onChanged 的时候, true 就不触发率
// 但是如果先 observe, 再 setValue的话, 是可以触发的. 因为 setValue 中, 将值都改成了 false, 表示可以触发.
if (observers.get(storeId) == null) {
observers.put(storeId, true);
}
// 调用 LiveData 的 observe 方法, 自定义 onChange 实现
super.observe(owner, t -> {
if (!observers.get(storeId)) { // false, 表示可以触发
observers.put(storeId, true); // 先修改 boolean 值, 然后触发
if (t != null || isAllowNullValue) {
observer.onChanged(t);//触发
}
}
});
}
@Override
protected void setValue(T value) {
if (value != null || isAllowNullValue) {
// 每次 setValue 的时候, 记得把所有的观察者的 boolen 值 改成 false, 表示可以触发
for (Map.Entry<Integer, Boolean> entry : observers.entrySet()) {
entry.setValue(false);
}
// 调用父类的
super.setValue(value);
}
}
protected void clear() {
super.setValue(null);
}
}
其思路也很清晰,为每个传入的observer对象携带一个布尔类型的值,作为其是否能进入observe方法的开关。每当有一个新的observer存进来的时候,开关默认关闭。
每次setValue后,打开所有Observer的开关,允许所有observe执行。
同时方法进去后,关闭当前执行的observer开关,即不能对其第二次执行了,除非你重新setValue。
通过这种机制,使得 不用反射技术实现LiveData的非粘性态 成为了可能。
就像古代自动关门的那个门栓. 第一次可以进, 进去之后自动就关住,就不能再进入了, 防止小偷. 除非怎么弄一下, 才能再进. 就像相当于 必须 setValue 一下, 才能再触发.