Skip to content

Latest commit

 

History

History
338 lines (236 loc) · 11.5 KB

6.LiveData.md

File metadata and controls

338 lines (236 loc) · 11.5 KB
Error in user YAML: (<unknown>): could not find expected ':' while scanning a simple key at line 14 column 1
---

title: 6.LiveData

date: 2019-06-11

categories: 

   - Kotlin

tags: 

   - Kotlin 

description: 

---

两种转化LiveData对象的方法:map()和switchMap()

Map()

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()函数可以用于对数据的封装

SwitchMap()

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让我们取消掉粘性事件.

解决

  1. 不要多次注册

  2. 反射修改 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的做法已经行不通了,这就是所谓的非粘性。

  1. 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 一下, 才能再触发.