"Bug"再现
在Activity中保存一个ViewModel,使用Navigation组件和Fragment实现页面的切换,AFragment获取Activity的ViewModel并注册LiveData数据为观察者,此时使用setValue让AFragment收到一次LiveData数据,然后切换到BFragment(AFragment销毁),之后切回AFragment,会发现重新注册LiveData数据,AFragment再次收到LiveData数据。
原因分析
官方介绍LiveData会一直向活跃的应用组件观察者发送数据,而使用Naviagtion组件时,博主实现的方案导致了每次切换页面都会重走一次Fragment的生命周期,也就是处于“STARTED 或 RESUMED 状态”,导致了从其他页面切换回来之后,会触发LiveData的数据回调。
这里其实是博主对于ViewModel的生命周期理解不够透彻,在AFragment中调用Activity中的ViewModel,导致ViewModel中的LiveData会一直给Fragment发送数据。
解决方案
- 合理管理ViewModel的范围,虽然ViewModel可以用来Fragment之间的数据共享,但如果业务范围只跟某个Fragment有关,那么最好就只给这个Fragment使用。这样Fragment在销毁或者创建的时候,也会销毁ViewModel与创建ViewModel,ViewModel携带的LiveData就是全新的不会在发送之前设置的数据。
- 使用一个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
}
}