经验之谈 - LiveData处理一次性消息(数据倒灌、粘性特性)

129 阅读5分钟

有一次面试时被问到什么是LiveData的数据倒灌,当时一脸问号,LiveData倒是经常用,这个词真是没听说过!结果被面试官狠狠的鄙视了一顿,甚至不愿过多解释,直接下一个问题!有点无语!

结果一查不就是Livedata的粘性消息嘛!也不知道是谁又造了这么个词儿,这一天天的!

LiveData在被订阅时,如果已经有旧数据了,则会把旧数据分发给新订阅者。这会导致处理一次性消息时(比如弹窗,Toast),有可能会触发多次。

这其实不算是Livedata的问题,LiveData设计出来就是为了保证数据状态和用户界面一致性的,用来处理一次性消息就有点为难它啦。

不过没关系,只需超简单封装下,就能弥补这个问题!

要想消息只分发一次有两种封装思路:

1. 除去粘性特性 // SingleLiveData类

优点:支持多Observe订阅、调用方式与LiveData保持一致

缺点:没有了消息的粘性特性,导致一些需要粘性特性的场景不能使用。(比如application初始化时请求全局配置接口提前拿到配置数据,但需要等到进入首页才弹升级对话框)

思路简介:再订阅数据时记录一个时间点,再数据更新时记录一个时间点,分发数据时进行时间点判断,只有订阅者订阅的时间早于数据更新的时间,才会被分发数据,即实现了订阅数据时旧数据不会分发给订阅者。

2. 记录订阅者ID // SingleLiveDataSticky类

优点:支持多Observe订阅,保留了粘性特性。

缺点:订阅数据方法,需要多传入一个订阅者ID。

思路简介:引入订阅者ID概念(为每一个订阅者分配一个唯一的ID),分发数据时先判断这个订阅者是否分发过数据,没有分发过则开始分发,然后记录该订阅者的分发状态。若已经分发过就不再分发了。

使用方法:

SingleLiveData使用方式与原始Livedata保持一致,就不示例啦。

image.png

封装逻辑比较简单,就不赘述啦,源码如下:

SingleLiveData实现:

/**
 *  作用:除去MutableLiveData自带的粘性事件特性,支持多Observe监听,使用方法和MutableLiveData保持一致。
 *  原理:通过记录订阅时间点、数据更新时间点,订阅操作之后更新的数据才会分发。
 */
public class SingleLiveData<T> {
    private final MutableLiveData<SingleEvent<T>> realLiveData = new MutableLiveData<>();

    private final HashMap<Observer<T>, TimeObserverWrapper> foreverObserverMaps = new HashMap<>();

    public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<T> observer) {
        realLiveData.observe(owner, new TimeObserverWrapper(observer));
    }


    public void observeForever(@NonNull Observer<T> observer) {
        TimeObserverWrapper wrapper = new TimeObserverWrapper(observer);
        foreverObserverMaps.put(observer, wrapper);
        realLiveData.observeForever(wrapper);
    }

    public void removeObserver(@NonNull Observer<T> observer) {
        TimeObserverWrapper observerWrapper = foreverObserverMaps.get(observer);
        if(observerWrapper == null) return;
        realLiveData.removeObserver(observerWrapper);
    }

    public void setValue(T value) {
        realLiveData.setValue(new SingleEvent<>(value));
    }

    public void postValue(T value) {
        realLiveData.postValue(new SingleEvent<>(value));
    }

    /**
     *  原始Observer的代理类,多增加一个createTime字段,用于记录订阅时间
     */
    private class TimeObserverWrapper implements Observer<SingleEvent<T>>{
        private final long createTime = SystemClock.elapsedRealtime(); //记录当前订阅操作时间(订阅时创建该对象)
        private final Observer<T> hostObserver;

        public TimeObserverWrapper(Observer<T> observer){
            hostObserver = observer;
        }


        @Override
        final public void onChanged(SingleEvent<T>  t) {
            if(t == null) return;
            if(createTime <= t.createTime){ //订阅者订阅的时间要早于数据更新的时间,才会向其分发数据
                if(hostObserver != null){
                    hostObserver.onChanged(t.getValue());
                }
            }
        }

    }

    /**
     * 原始数据代理类,多增加一个createTime字段,用于记录数据创建/更新的时间。
     */
    private static class  SingleEvent<T> {
        protected long createTime = SystemClock.elapsedRealtime(); //记录数据更新时间(数据更新时创建该对象)

        private final T value;

        public SingleEvent(T value){
            this.value = value;
        }

        public T getValue(){
            return value;
        }
    }
}

SingleLiveDataSticky实现:

/**
 * 作用:数据不更新情况下,对一个订阅者 数据只分发一次(同一个订阅者的再次订阅不会分发旧数据);
 *      既实现了一次数据更新只分发一次,也保留了livedata的粘性特性。
 * 原理:每一个订阅者分配一个固定唯一的ID,数据分发后记录该订阅者ID。数据分发时 查询当前订阅者ID如果已经分发过了,就不分发了。
 */
public class SingleLiveDataSticky<T> {
    private final MutableLiveData<SingleEvent<T>> realLiveData = new MutableLiveData<>();

    private final HashMap<Observer<T>, TimeObserverWrapper> foreverObserverMaps = new HashMap<>();

    /**
     * 当只有一个Observe订阅者时 可以使用该方法
     */
    public void observeDefaultID(@NonNull LifecycleOwner owner,  @NonNull Observer<T> observer) {
        observe(owner, Integer.MAX_VALUE, observer);
    }

    public void observe(@NonNull LifecycleOwner owner, int observeID, @NonNull Observer<T> observer) {
        realLiveData.observe(owner, new TimeObserverWrapper(observeID, observer));
    }


    public void observeForever(int observePos, @NonNull Observer<T> observer) {
        TimeObserverWrapper wrapper = new TimeObserverWrapper(observePos, observer);
        foreverObserverMaps.put(observer, wrapper);
        realLiveData.observeForever(wrapper);
    }

    public void removeObserver(@NonNull Observer<T> observer) {
        TimeObserverWrapper observerWrapper = foreverObserverMaps.get(observer);
        if(observerWrapper == null) return;
        realLiveData.removeObserver(observerWrapper);
    }

    public void setValue(T value) {
        realLiveData.setValue(new SingleEvent<>(value));
    }

    public void postValue(T value) {
        realLiveData.postValue(new SingleEvent<>(value));
    }

    /**
     * 原始Observer代理类,控制数据分发逻辑
     */
    private class TimeObserverWrapper implements Observer<SingleEvent<T>>{
        private final int observeId; //每一个订阅者对应唯一的订阅ID
        private final Observer<T> hostObserver;

        public TimeObserverWrapper(int observePos, Observer<T> observer){
            this.observeId = observePos;
            hostObserver = observer;
        }


        @Override
        final public void onChanged(SingleEvent<T>  t) {
            if(t == null) return;
            if(hostObserver != null && !t.isIdConsumed(observeId)){ //没有被分发过的订阅者 才进行数据分发
                hostObserver.onChanged(t.getValue());
                t.idConsume(observeId);
            }
        }

    }

    /**
     * 原始数据代理类,记录订阅者的分发状态。
     */
    private static class  SingleEvent<T> {
        private final HashMap<Integer, Boolean> id_consumeStateMap = new HashMap<>(); //记录订阅者的分发状态

        private final T value;

        public void idConsume(int id){
            id_consumeStateMap.put(id, true);
        }

        public boolean isIdConsumed(int id){
            return Boolean.TRUE.equals(id_consumeStateMap.get(id));
        }

        public SingleEvent(T value){
            this.value = value;
        }

        public T getValue(){
            return value;
        }
    }
}

使用建议:

如果一次性消息不需要粘性特性,使用SingleLiveData,使用简单。

如果一次性消息需要粘性特性,使用SingleLiveDataSticky,需要为每一个订阅者分配一个固定唯一的ID;只有一个订阅者也可以使用无ID参数的observeDefaultID()方法,就不需要传入订阅者ID啦。

工程完整地址:github.com/High-Power-…

欢迎评论区交流!!!