Android --- 解决LiveData数据倒灌的新思路

博文参考:https://blog.csdn.net/Gaga246/article/details/137143519
数据倒灌现象

对于LiveData“数据倒灌”的问题,我相信很多人已经都了解了,这里提一下。所谓的“数据倒灌”:其实是类似粘性广播那样,当新的观察者开始注册观察时,会把上次发的最后一次的历史数据传递给当前注册的观察者。

比如在在下面的例子代码中:

val testViewModel = ViewModelProvider(this)[TestViewModel::class.java]
testViewModel.updateData("第一次发送数据")
testViewModel.testLiveData.observe(this,object :Observer<String>{
    override fun onChanged(value: String) {
        println("==============$value")
    }
})

updateData方法发送了一次数据,当下面调用LiveData的observe方法时,会立即打印==============第一次发送数据,这就是上面说的“数据倒灌”现象。
发生原因

原因其实也很简单,其实就是 LiveData内部有一个mVersion字段,记录版本,其初始的 mVersion 是-1,当我们调用了其 setValue 或者 postValue,其 mVersion 会+1;对于每一个观察者的封装 ObserverWrapper,其初始 mLastVersion 也为-1,也就是说,每一个新注册的观察者,其 mLastVersion 为-1;当 LiveData 设置这个 ObserverWrapper 的时候,如果 LiveData 的 mVersion 大于 ObserverWrapper 的 mLastVersion,LiveData 就会强制把当前 value 推送给 Observer。

也就是下面这段代码

private void considerNotify(ObserverWrapper observer) {
    if (!observer.mActive) {
        return;
    }

    if (!observer.shouldBeActive()) {
        observer.activeStateChanged(false);
        return;
    }
    // 判断observer的版本是否大于LiveData的版本mVersion
    if (observer.mLastVersion >= mVersion) {
        return;
    }
    observer.mLastVersion = mVersion;
    observer.mObserver.onChanged((T) mData);
}

所以要解决这个问题,思路上有两种方式:

通过改变每个ObserverWrapper的版本号的值
通过某种方式,保证第一次分发不响应

解决方法

目前网络上可以看到有三种解决方式
每次只响应一次

public class SingleLiveData<T> extends MutableLiveData<T> {
    private final AtomicBoolean mPending = new AtomicBoolean(false);

    public SingleLiveData() {
    }

    public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer) {
        super.observe(owner, (t) -> {
            if (this.mPending.compareAndSet(true, false)) {
                observer.onChanged(t);
            }

        });
    }

    @MainThread
    public void setValue(@Nullable T t) {
        this.mPending.set(true);
        super.setValue(t);
    }

    @MainThread
    public void call() {
        this.setValue((Object)null);
    }
}

这个方法能解决历史数据往回发的问题,但是对于多Observe监听就不行了,只能单个监听,如果是多个监听,只有一个能正常收到,其他的就无法正常工作
反射

这种方式就是每次注册观察者时,通过反射获取LiveData的版本号,然后又通过反射修改当前Observer的版本号值。这种方式的优点是:

能够多 Observer 监听
解决粘性问题

但是也有缺点:

每次注册 observer 的时候,都需要反射更新版本,耗时有性能问题

UnPeekLiveData

public class UnPeekLiveData extends LiveData {

    protected boolean isAllowNullValue;

    private final HashMap observers = new HashMap();

    public void observeInActivity(@NonNull AppCompatActivity activity, @NonNull Observer super T> observer) {
        LifecycleOwner owner = activity;
        Integer storeId = System.identityHashCode(observer);
        observe(storeId, owner, observer);
    }

    private void observe(@NonNull Integer storeId,
                         @NonNull LifecycleOwner owner,
                         @NonNull Observer super T> observer) {

        if (observers.get(storeId) == null) {
            observers.put(storeId, true);
        }

        super.observe(owner, t -> {
            if (!observers.get(storeId)) {
                observers.put(storeId, true);
                if (t != null || isAllowNullValue) {
                    observer.onChanged(t);
                }
            }
        });
    }

    @Override
    protected void setValue(T value) {
        if (value != null || isAllowNullValue) {
            for (Map.Entry entry : observers.entrySet()) {
                entry.setValue(false);
            }
            super.setValue(value);
        }
    }

    protected void clear() {
        super.setValue(null);
    }
}

这个其实就是上面 SingleLiveData 的升级版,SingleLiveData 是用一个变量控制所有的 Observer,而上面采用的每个 Observer 都采用一个控制标识进行控制。 每次 setValue 的时候,就打开所有 Observer 的开关,表示可以接受分发。分发后,关闭当前执行的 Observer 开关,即不能对其第二次执行了,除非你重新 setValue。 这种方式基本上是比价完美了,除了内部多一个用HashMap存放每个Observer的标识,如果Observer比较多的话,会有一定的内存消耗。
新的思路

我们先看下LiveData获取版本号方法:

int getVersion() {
    return mVersion;
}

这个方法是一个包访问权限的方法,如果我新建一个和LiveData同包名的类,是不是就可以不需要反射就能获取这个值呢?其实这是可行的

// 跟LiveData同包名
package androidx.lifecycle

open class SafeLiveData<T> : MutableLiveData<T>() {

    override fun observe(owner: LifecycleOwner, observer: Observer<in T>) {
        // 直接可以通过this.version获取到版本号
        val pictorialObserver = PictorialObserver(observer, this.version > START_VERSION)
        super.observe(owner, pictorialObserver)
    }

    class PictorialObserver<T>(private val realObserver: Observer<in T>, private var preventDispatch: Boolean = false) :
        Observer<T> {

        override fun onChanged(value: T) {
            // 如果版本有差异,第一次不处理
            if (preventDispatch) {
                preventDispatch = false
                return
            }
            realObserver.onChanged(value)
        }

    }
}

java代码:

package androidx.lifecycle;

import androidx.annotation.NonNull;

public class SafeLiveData<T> extends MutableLiveData<T> {
    PictorialObserver version;

    @Override
    public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer) {
        version = new PictorialObserver(observer,getVersion() > START_VERSION);
        super.observe(owner, observer);
    }

    public static class PictorialObserver<T> implements Observer<T> {
        //     ? super T 意味着 realObserver 可以接受 T 类型及其任意父类型的对象,这样可以增加代码的灵活性。
        //例子解释:
        //    如果 T 是 Number,那么 Observer<? super T> 可以是 Observer<Number>, Observer<Object>, 甚至 Observer<Serializable>
        private final Observer<? super T> realObserver;
        private boolean preventDispatch;

        public PictorialObserver(Observer<? super T> observer, boolean b) {
            this.realObserver = observer;
            this.preventDispatch = preventDispatch;
        }

        @Override
        public void onChanged(T value) {
            // 如果版本号有差异不处理
            if (preventDispatch) {
                preventDispatch = false;
                return;
            }
            realObserver.onChanged(value);
        }
    }
}

这种取巧的方式的思路就是:

利用同包名访问权限可以获取版本号,不需要通过反射获取
判断LiveData和Observer是否有版本差异,如果有,第一次不响应,否则就响应

我个人是偏向这种方式,也应用到了实际的开发中。这种方式的优点是:改动小,不需要反射,也不需要用HashMap存储等,缺点是:有一定的侵入性,假如后面这个方法的访问权限修改或者包名变动,就无效了,但是我认为这种可能性是比较小,毕竟androidx库迭代了这么多版本,算是比较稳定了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值