Android LiveData阻止在观察时收到最后一个值

Pav*_*ley 7 android android-livedata android-architecture-components mutablelivedata

LiveData开始观察时是否可以防止接收到最后一个值?我正在考虑LiveData用作事件。

例如类似于show message,导航事件或对话框触发器之类的事件EventBus

ViewModel与片段之间的通信有关的问题,Google给我们LiveData提供了用数据更新视图的方法,但是这种类型的通信不适用于仅需通过单个事件更新视图一次的情况,也无法保存视图的引用ViewModel并调用某些方法,因为它将导致内存泄漏。

我发现了类似的SingleLiveEvent-但它仅适用于1个观察者,不适用于多个观察者。

Jur*_*lja 13

我在 MutableLiveData 中使用 Google Samples 中的这个 EventWraper 类

/**
 * Used as a wrapper for data that is exposed via a LiveData that represents an event.
 */
public class Event<T> {

    private T mContent;

    private boolean hasBeenHandled = false;


    public Event( T content) {
        if (content == null) {
            throw new IllegalArgumentException("null values in Event are not allowed.");
        }
        mContent = content;
    }
    
    @Nullable
    public T getContentIfNotHandled() {
        if (hasBeenHandled) {
            return null;
        } else {
            hasBeenHandled = true;
            return mContent;
        }
    }
    
    public boolean hasBeenHandled() {
        return hasBeenHandled;
    }
}
Run Code Online (Sandbox Code Playgroud)

在视图模型中:

 /** expose Save LiveData Event */
 public void newSaveEvent() {
    saveEvent.setValue(new Event<>(true));
 }

 private final MutableLiveData<Event<Boolean>> saveEvent = new MutableLiveData<>();

 public LiveData<Event<Boolean>> onSaveEvent() {
    return saveEvent;
 }
Run Code Online (Sandbox Code Playgroud)

在活动/片段中

mViewModel
    .onSaveEvent()
    .observe(
        getViewLifecycleOwner(),
        booleanEvent -> {
          if (booleanEvent != null)
            final Boolean shouldSave = booleanEvent.getContentIfNotHandled();
            if (shouldSave != null && shouldSave) saveData();
          }
        });
Run Code Online (Sandbox Code Playgroud)


小智 8

面对同样的问题,我创建了一些简单的 kotlin 扩展函数,可以轻松解决问题。

用法如下:

val liveData = MutableLiveData<String>()
liveData.value = "Hello"

val freshResult = mutableListOf<String>()
val normalResult = mutableListOf<String>()

liveData.observeForeverFreshly(Observer {
    freshResult.add(it)
})

liveData.observeForever(Observer {
    normalResult.add(it)
})

liveData.value = "World"

assertEquals(listOf("World"), freshResult)
assertEquals(listOf("Hello", "World"), normalResult)
Run Code Online (Sandbox Code Playgroud)

基本源代码解释为 blow。

更多细节(支持一些特殊情况,例如MediatorLiveData从Transformations.map返回),可以在github中查看:livedata-ext

FreshLiveData.kt

fun <T> LiveData<T>.observeFreshly(owner: LifecycleOwner, observer: Observer<in T>) { 
    // extention fuction to get LiveData's version, will explain in below.
    val sinceVersion = this.version()
    this.observe(owner, FreshObserver<T>(observer, this, sinceVersion))
}

fun <T> LiveData<T>.observeForeverFreshly(observer: Observer<in T>, skipPendingValue: Boolean = true) {
    val sinceVersion = this.version()
    this.observeForever(FreshObserver<T>(observer, this, sinceVersion))
}

// Removes the observer which has been previously observed by [observeFreshly] or [observeForeverFreshly].
fun <T> LiveData<T>.removeObserverFreshly(observer: Observer<in T>) {
    this.removeObserver(FreshObserver<T>(observer, this, 0))
}

class FreshObserver<T>(
    private val delegate: Observer<in T>,
    private val liveData: LiveData<*>,
    private val sinceVersion: Int
) : Observer<T> {

    override fun onChanged(t: T) {
        if (liveData.version() > sinceVersion) {
            delegate.onChanged(t)
        }
    }

    override fun equals(other: Any?): Boolean {
        if (this === other) return true
        if (javaClass != other?.javaClass) return false
        if (delegate != (other as FreshObserver<*>).delegate) return false
        return true
    }

    override fun hashCode(): Int {
        return delegate.hashCode()
    }
}

Run Code Online (Sandbox Code Playgroud)

因为我们需要访问LiveData的pcakage visibile方法getVersion()进行比较,所以在包android.arch.lifecycleandroidx.lifecycle(AndroidX)中创建一个类:

LiveDataHiddenApi.kt

package androidx.lifecycle

fun LiveData<*>.version(): Int {
    return this.getVersion()
}
Run Code Online (Sandbox Code Playgroud)


d4v*_*idi 7

有了 RxJava 的一些经验,我已经习惯于认为这样的行为要求通常是ObserveableLiveData在我们的例子中)的问题。有许多运算符,例如replay(),与用户进行的实际发布相比,它们可以控制实际发出的内容(以及何时)。本质上,SingleLiveEvent对它也有同样的概念。

因此,我想出了这个修改后的MutableLiveData调用实现VolatileLiveData

open class VolatileLiveData<T> : MutableLiveData<T>() {
    private val lastValueSeq = AtomicInteger(0)
    private val wrappers = HashMap<Observer<in T>, Observer<T>>()

    @MainThread
    public override fun setValue(value: T) {
        lastValueSeq.incrementAndGet()
        super.setValue(value)
    }

    @MainThread
    public override fun observe(owner: LifecycleOwner, observer: Observer<in T>) {
        val observerWrapper = ObserverWrapper(lastValueSeq, observer)
        wrappers[observer] = observerWrapper
        super.observe(owner, observerWrapper)
    }

    @MainThread
    public override fun observeForever(observer: Observer<in T>) {
        val observerWrapper = ObserverWrapper(lastValueSeq, observer)
        wrappers[observer] = observerWrapper
        super.observeForever(observerWrapper)
    }

    @MainThread
    public override fun removeObserver(observer: Observer<in T>) {
        val observerWrapper = wrappers[observer]
        observerWrapper?.let {
            wrappers.remove(observerWrapper)
            super.removeObserver(observerWrapper)
        }
    }
}

private class ObserverWrapper<T>(private var currentSeq: AtomicInteger, private val observer: Observer<in T>) : Observer<T> {
    private val initialSeq = currentSeq.get()
    private var _observer: Observer<in T> = Observer {
        if (currentSeq.get() != initialSeq) {
            // Optimization: this wrapper implementation is only needed in the beginning.
            // Once a valid call is made (i.e. with a different concurrent sequence), we
            // get rid of it any apply the real implementation as a direct callthrough.
            _observer = observer
            _observer.onChanged(it)
        }
    }

    override fun onChanged(value: T) {
        _observer.onChanged(value)
    }
}
Run Code Online (Sandbox Code Playgroud)

首先,与@emandt 类似,我将唯一序列与每个实时值相关联——但严格限制在实时数据本身的范围内。每当将值设置为实时数据时,就会设置此序列。

其次,受 启发SingleLiveData,我在用户的观察者周围引入了包装器,仅当序列不同时才调用它(即,自订阅以来已设置值)。

这基本上是总结,但是对于完整的文档,请转到我的要点

用法

至于使用它 - 如果您可以完全控制它LiveData,只需VolatileLiveData像使用MutableLiveData. 如果数据最初来自其他地方(例如 Room),Transformations.switchMap()则可以使用它来“切换”到易失性实现。


Ber*_*yle 5

如果您按原样使用它们,我认为在开始观察时不可能阻止 LiveData 接收到最后一个值。您可以做的是扩展ViewModel并使其仅在添加观察者时通知视图。

另一种选择是简单地忽略回调

  1. 向 ViewModel 添加一个标志。

    private boolean isFirstTime = true;
    
    public boolean isFirstTime() { return isFirstTime; }
    
    public boolean onObserverAdded() { isFirstTime = false; }`
    
    Run Code Online (Sandbox Code Playgroud)
  2. 在回调中添加检查

    @Override
    public void onChanged(@Nullable final String newName) {
    boolean ignore = ((MyViewModel)ViewModelProviders.of(MyActivity.this).get(MyViewModel.class)).isFirstTime();
    if(ignore) return;
    
    // Update the UI
    }
    
    Run Code Online (Sandbox Code Playgroud)
  3. 最后onObserverAdded()在添加观察者后调用。


ema*_*ndt 5

我创建了一个新的类来保存我的真实数据和一个“特殊 ID”:

class LiveDataItem {
    long mRealtimeNanos;
    YOUR_PREVIOUS_LIVEDATA_TYPE mData;
    LiveDataItem(YOUR_PREVIOUS_LIVEDATA_TYPE data, long realtimeNanos) {
        this.mRealtimeNanos = realtimeNanos;
        this.mData = data;
    }
}
Run Code Online (Sandbox Code Playgroud)

然后我创建了一个新的“全局”变量:

final List<Long> mExcludedRealtimeNanos = new ArrayList<>;
Run Code Online (Sandbox Code Playgroud)

在这一点上,我选择通过一个新的和自定义的“postValue()”方法来“设置/postValue()”我的“LiveDataItem”类型而不是原始的“YOUR_PREVIOUS_LIVEDATA_TYPE”类型:

public void myPostValue(YOUR_PREVIOUS_LIVEDATA_TYPE data, boolean notifyWhenObserved) {
    long cRealtimeNanos = SystemClock.realtimeNanos();
    if (!notifyWhenObserved) mExcludedRealtimeNanos.add(cRealtimeNanos);
    ....postValue(new LiveDataItem(data, cRealtimeNanos));
}
Run Code Online (Sandbox Code Playgroud)

然后我创建了一个普通的观察者,它将接收所有“Changed()”事件,并在其中检查“RealtimeNanos”:

public void onChanged(LiveDataItem myDataItem) {
    boolean cFound = false;
    for (Long cRealtimeNanos : mExcludedRealtimeNanos) {
        if (cRealtimeNanos == myDataItem.mRealtimeNanos) {
            cFound = true;
            break;
        }
    }
    //check if it was found --> NO: it means that I wish to get the notification
    if (!cFound) mMyOnChangedCallback(myDataItem.mData)
}
Run Code Online (Sandbox Code Playgroud)

显然,“mMyOnChangedCallback()”方法是一个回调函数,只要原始“onChanged()”事件被引发,就会被调用,但只有当您设置在数据创建期间通知它时才会调用。

您可以选择仅通过从“mExcludedRealtimeNanos”中删除 THAT RealtimeNanos 然后将新的观察者附加到该 LiveData 来再次收到通知。

很少有更改可以改进此代码,但我写了我记得我的旧代码的内容(此时此刻我不在电脑旁)。例如,当使用我们的自定义 postValue() 方法发布新数据时,我们可以决定从“mExcludedRealtimeNanos”中删除一个值......