LiveData和Fragment重复触发数据回调BUG

1,077 阅读2分钟

"Bug"再现

在Activity中保存一个ViewModel,使用Navigation组件和Fragment实现页面的切换,AFragment获取Activity的ViewModel并注册LiveData数据为观察者,此时使用setValue让AFragment收到一次LiveData数据,然后切换到BFragment(AFragment销毁),之后切回AFragment,会发现重新注册LiveData数据,AFragment再次收到LiveData数据。

image.png

原因分析

LiveData 官网介绍

官方介绍LiveData会一直向活跃的应用组件观察者发送数据,而使用Naviagtion组件时,博主实现的方案导致了每次切换页面都会重走一次Fragment的生命周期,也就是处于“STARTED 或 RESUMED 状态”,导致了从其他页面切换回来之后,会触发LiveData的数据回调。 这里其实是博主对于ViewModel的生命周期理解不够透彻,在AFragment中调用Activity中的ViewModel,导致ViewModel中的LiveData会一直给Fragment发送数据。

解决方案

  1. 合理管理ViewModel的范围,虽然ViewModel可以用来Fragment之间的数据共享,但如果业务范围只跟某个Fragment有关,那么最好就只给这个Fragment使用。这样Fragment在销毁或者创建的时候,也会销毁ViewModel与创建ViewModel,ViewModel携带的LiveData就是全新的不会在发送之前设置的数据。
  2. 使用一个google大神实现的一个复写类 SingleLiveEvent,其中的机制是用一个原子 AtomicBoolean记录一次setValue。在发送一次后在将AtomicBoolean设置为false,阻止后续前台重新触发时的数据发送。
package jp.wasabeef.util

import android.arch.lifecycle.LifecycleOwner
import android.arch.lifecycle.MutableLiveData
import android.arch.lifecycle.Observer
import android.support.annotation.MainThread
import android.support.annotation.Nullable
import timber.log.Timber
import java.util.concurrent.atomic.AtomicBoolean

/**
 * A lifecycle-aware observable that sends only new updates after subscription, used for events like
 * navigation and Snackbar messages.
 * <p>
 * This avoids a common problem with events: on configuration change (like rotation) an update
 * can be emitted if the observer is active. This LiveData only calls the observable if there's an
 * explicit call to setValue() or call().
 * <p>
 * Note that only one observer is going to be notified of changes.
 */
class SingleLiveEvent<T> : MutableLiveData<T>() {

    private val mPending = AtomicBoolean(false)

    @MainThread
    override fun observe(owner: LifecycleOwner, observer: Observer<T>) {

        if (hasActiveObservers()) {
            Timber.w("Multiple observers registered but only one will be notified of changes.")
        }

        // Observe the internal MutableLiveData
        super.observe(owner, Observer<T> { t ->
            if (mPending.compareAndSet(true, false)) {
                observer.onChanged(t)
            }
        })
    }

    @MainThread
    override fun setValue(@Nullable t: T?) {
        mPending.set(true)
        super.setValue(t)
    }

    /**
     * Used for cases where T is Void, to make calls cleaner.
     */
    @MainThread
    fun call() {
        value = null
    }
}