简单粗暴解决LiveData『数据倒灌』的问题

本文介绍了数据倒灌问题在LiveData中的表现及其原因,并详细探讨了现有的解决方案,如Event事件包装器、反射干预Version、SingleLiveEvent和UnPeekLiveData的优缺点。接着提出了一种名为UnFlowLiveData的简单解决方案,它在observe时创建新的LiveData实例,避免旧数据回调。UnFlowLiveData的实现代码简洁,易于理解,适合需要防止数据倒灌的场景。

解决【数据倒灌】问题方案二:SingleLiveData:解决LiveData『数据倒灌』的问题(方案二)

1、什么是数据倒灌?

一句话总结就是:先给LiveData设置了value,然后监听者才开始对LiveData进行监听,这时LiveData的value会立马回调给监听者。

虽然从google设计者的角度来看,这并不是一个设计缺陷,但从我们使用者角度来看,其实很多场景下这并不是我们想要的。

我们更期望的是:只收到对LiveData开始监听之后的value,开始监听之前的旧value不要回调给我。

2、数据倒灌的根本原因

LiveData每次setValuepostValuemVersion都会自增:

    @MainThread
    protected void setValue(T value) {
        assertMainThread("setValue");
        mVersion++;
        mData = value;
        dispatchingValue(null);
    }

当调用LiveData进行observe时,最终会调到如下方法:

    private void considerNotify(ObserverWrapper observer) {
        ...
        
        if (observer.mLastVersion >= mVersion) {
            return;
        }
        observer.mLastVersion = mVersion;
        //noinspection unchecked
        observer.mObserver.onChanged((T) mData);
    }

可以看到,当observermLastVersion小于mVersion时就会把之前的数据回调给监听者

另外,observer的mLastVersion的初始值为-1

static final int START_VERSION = -1;
...

int mLastVersion = START_VERSION;

总结:当我们setValue时,mVersion会从-1开始自增,之后我们去observe时,由于observermLastVersion的初始值是-1,比mVersion小,所以监听者observe时,会立马把旧的数据回调给监听者。

3、现有解决方案及各自缺陷

  1. Event 事件包装器

    对于多观察者的情况,只允许第一个观察者消费,这不符合现实需求;

    而且手写 Event 事件包装器,在 Java 中存在 null 安全的一致性问题。

  2. 反射干预 Version 的方式

    存在延迟,无法用于对实时性有要求的场景;

    并且数据会随着 SharedViewModel 长久滞留在内存中得不到释放。

  3. 官方最新 demo 中的 SingleLiveEvent

    是对 Event 事件包装器 一致性问题的改进,但未解决多观察者消费的问题;

    而且额外引入了消息未能从内存中释放的问题。

  4. UnPeekLiveData

    提出【数据倒灌】名词大佬写的一个库UnPeekLiveData

    缺点:改动大,逻辑复杂,小专栏收费

4、解决方案-UnFlowLiveData

解决的方案有很多,这里介绍一种简单粗暴,侵入性小,一看就懂的方案。

方案思路:

  1. 在observe/observeForever时创建新的LiveData,并且根据observer保存该LiveData到mObserverMap中,而且该LiveData订阅相关的observer;

  2. 当postValue/setValue时,遍历mObserverMap的所有LiveData,并把值设置给LiveData;

完整代码:

public class UnFlowLiveData<T> {
    private final Handler mMainHandler;
    private T mValue;
    private final ConcurrentHashMap<Observer<? super T>, MutableLiveData<T>> mObserverMap;

    public UnFlowLiveData() {
        mMainHandler = new Handler(Looper.getMainLooper());
        mObserverMap = new ConcurrentHashMap<>();
    }

    @MainThread
    public void observeForever(@NonNull Observer<? super T> observer) {
        checkMainThread("observeForever");
        MutableLiveData<T> liveData = new MutableLiveData<>();
        // 该LiveData也observeForever该observer,这样setValue时,能把value回调到onChanged中
        liveData.observeForever(observer);
        mObserverMap.put(observer, liveData);
    }

    @MainThread
    public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer) {
        checkMainThread("observe");
        Lifecycle lifecycle = owner.getLifecycle();
        if (lifecycle.getCurrentState() == DESTROYED) {
            // ignore
            return;
        }
        lifecycle.addObserver(new LifecycleObserver() {

            @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
            public void onDestroy() {
                mObserverMap.remove(observer);
                lifecycle.removeObserver(this);
            }
        });
        MutableLiveData<T> liveData = new MutableLiveData<>();
        // 该LiveData也observe该observer,这样setValue时,能把value回调到onChanged中
        liveData.observe(owner, observer);
        mObserverMap.put(observer, liveData);
    }

    @MainThread
    public void removeObserver(@NonNull final Observer<? super T> observer) {
        checkMainThread("removeObserver");
        mObserverMap.remove(observer);
    }

    public T getValue() {
        return mValue;
    }

    public void clearValue() {
        mValue = null;
    }

    @MainThread
    public void setValue(T value) {
        checkMainThread("setValue");
        mValue = value;
        // 遍历所有LiveData,并把value设置给LiveData
        for (MutableLiveData<T> liveData : mObserverMap.values()) {
            liveData.setValue(value);
        }
    }

    public void postValue(T value) {
        mMainHandler.post(() -> setValue(value));
    }

    private void checkMainThread(String methodName) {
        if (Looper.myLooper() != Looper.getMainLooper()) {
            throw new IllegalStateException("UnFlowLiveData, Cannot invoke " + methodName
                    + " on a background thread");
        }
    }
}

5、结尾

UnFlowLiveData方案很简单,代码也不多,接口都是保持跟LiveData保持一致,使用起来也方便。这对于既想利用LiveData的优点,又不希望【数据倒灌】的开发者来说,这不失为一个不错的方案。


参考文章

https://blog.csdn.net/ljcITworld/article/details/112849126

https://www.jianshu.com/p/e08287ec62cd

https://www.jianshu.com/p/f3158a4e0bc8

<think>嗯,我现在需要解决Android中的LiveData数据倒灌问题。让我先理解一下什么是数据倒灌。根据引用[2],数据倒灌类似于粘性广播,当新的观察者注册时,LiveData会把最后一次的数据传递给它。比如在Fragment或Activity重新创建后,LiveData会立即发送旧数据,这可能导致重复处理或者界面显示错误。 那用户提到的解决方案,引用[1]里有几种措施,比如使用SingleLiveEvent或者反射修改version。还有引用[3]提到了引入依赖包,可能和正确使用LiveData组件有关。另外引用[4]中的例子显示在旋转屏幕后出现数据重复加载的问题,可能是因为LiveData保存了之前的状态,新的观察者接收到了旧数据。 首先,我需要回顾常见的解决方案。比如SingleLiveEvent,这是一个限制只能发送一次事件的类。不过根据引用[1],这种方法可能有局限性,比如多个观察者只能有一个接收到事件。然后还有事件包装类,比如将数据包裹在Event类中,通过标记来判断是否已处理。这样可以避免重复消费。 另外引用[5]提到LiveData的作用域问题,如果LiveData存在于ViewModel中,而ViewModel的生命周期长于Fragment,当Fragment重建时,LiveData仍然持有旧数据,导致倒灌。所以可能需要确保ViewModel的正确作用域,或者在数据更新时清除旧数据。 还有引用[3]中的依赖包是否正确引入,比如是否使用了AndroidX的Lifecycle组件。正确的依赖可能帮助避免一些问题,但主要问题还是在于如何处理数据的发送和接收。 现在思考解决方案: 1. 事件包装类(Event Wrapper):在数据层外面包装一个Event类,里面有一个标记位,记录是否已经被处理。观察者检查这个标记,如果已经处理过则忽略。这种方法需要在每次发送数据时创建新的Event实例,并在观察后标记为已处理。 2. SingleLiveEvent:这是一个自定义的LiveData子类,重写observe方法,确保每次数据变化后只能被观察一次。但要注意多观察者的情况,可能只有最后一个观察者能收到事件。 3. 反射修改Version:通过反射修改LiveData内部的版本号,使得新观察者不会收到旧数据。但这种方法可能不兼容不同系统版本,存在风险。 4. 使用Kotlin的Channel或SharedFlow:如果项目已经迁移到Kotlin协程,可以考虑使用Flow替代LiveData,尤其是SharedFlow,可以配置重放次数为0,避免数据倒灌。 5. UnPeekLiveData:有些开源库提供了封装好的解决方案,比如UnPeekLiveData,它内部处理了数据的分发逻辑,防止重复发送。 然后需要评估每种方法的优缺点。例如,事件包装类比较灵活,但需要额外的代码;SingleLiveEvent适合单次事件,但多观察者可能有问题;反射方法可能不稳定;使用Flow需要项目支持协程,但更现代化。 用户可能希望具体的实现步骤,所以需要给出代码示例。例如,Event类的实现,如何在ViewModel中包装数据,以及在观察时的处理。 另外,引用[1]中提到“简单粗暴解决数据倒灌”,可能指的是直接使用反射或者重置Version的方法。但这种方法需要谨慎,因为可能破坏LiveData的内部状态。 总结,最推荐的可能是事件包装类和UnPeekLiveData。给出这两种方法的详细实现,并说明注意事项。例如,在Event类中,如何处理多个观察者,或者如何确保标记位的正确性。 最后,需要验证这些解决方案是否适用于用户的具体情况,比如是否处理了旋转屏幕后的数据恢复,或者多个Fragment之间通信的问题。同时,提醒用户根据项目需求选择合适的方案,并注意测试不同场景下的表现。</think>### Android LiveData数据倒灌解决方案 数据倒灌指新注册的观察者接收到LiveData最后一次的历史数据,可能导致重复操作或界面异常。以下是主流解决方案: --- #### 一、事件包装法(推荐) 通过包装数据对象,标记事件是否已被消费。 **实现步骤**: 1. 定义事件包装类: ```kotlin class Event<T>(private val content: T) { private var hasBeenHandled = false fun getContentIfNotHandled(): T? { return if (hasBeenHandled) null else { hasBeenHandled = true content } } } ``` 2. ViewModel中使用Event包装数据: ```kotlin class MyViewModel : ViewModel() { private val _data = MutableLiveData<Event<String>>() val data: LiveData<Event<String>> = _data fun loadData() { _data.value = Event("新数据") } } ``` 3. 观察时处理未消费事件: ```kotlin viewModel.data.observe(this) { event -> event.getContentIfNotHandled()?.let { data -> // 处理数据(仅触发一次) } } ``` **优点**:灵活可控,适用于多观察者场景[^1]。 --- #### 二、SingleLiveEvent(限单次事件) 自定义LiveData子类,确保数据仅分发一次。 **实现**: ```kotlin class SingleLiveEvent<T> : MutableLiveData<T>() { private val pending = AtomicBoolean(false) override fun observe(owner: LifecycleOwner, observer: Observer<in T>) { super.observe(owner) { t -> if (pending.compareAndSet(true, false)) { observer.onChanged(t) } } } override fun setValue(value: T) { pending.set(true) super.setValue(value) } } ``` **限制**:多个观察者时仅最后一个生效,适合弹窗、导航等单次事件[^2]。 --- #### 三、UnPeekLiveData(开源方案) 使用第三方库(如[UnPeek-LiveData](https://github.com/KunMinX/UnPeek-LiveData))自动管理数据可见性: ```gradle implementation 'com.kunminx.arch:unpeek-livedata:7.8.0' ``` **使用方式**: ```kotlin val liveData = UnPeekLiveData<String>() liveData.observeInActivity(activity) { data -> // 仅接收新数据,避免倒灌 } ``` **优点**:无需手动标记,简化代码[^4]。 --- #### 四、反射重置Version(谨慎使用) 通过反射修改LiveData内部版本号,跳过历史数据: ```kotlin fun <T> LiveData<T>.clearStickyData() { val mVersionField = LiveData::class.java.getDeclaredField("mVersion") mVersionField.isAccessible = true mVersionField.set(this, mVersionField.get(this) as Int + 1) } ``` **风险**:反射破坏封装性,可能引发兼容性问题[^1]。 --- #### 五、改用Kotlin Flow 若项目支持协程,可用`SharedFlow`替代LiveData: ```kotlin private val _data = MutableSharedFlow<String>(replay = 0) // 不重放历史数据 val data: SharedFlow<String> = _data.asSharedFlow() // 发送数据 viewModelScope.launch { _data.emit("新数据") } // 观察数据 lifecycleScope.launch { viewModel.data.collect { data -> // 处理数据(无倒灌) } } ``` **优势**:天然支持响应式编程,避免粘性问题[^5]。 --- ### 选择建议 | 场景 | 推荐方案 | |------|----------| | 通用事件分发 | 事件包装法 | | 单次触发操作(如弹窗) | SingleLiveEvent | | 现代化架构(已用协程) | Kotlin Flow | | 快速集成 | UnPeekLiveData | **注意事项**: - 避免在`ViewModel`中直接暴露`MutableLiveData`,应通过`LiveData`对外暴露[^3]。 - 跨Fragment通信时,确保`ViewModel`的作用域正确(如`by activityViewModels()`)[^5]。 ---
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值