LiveData 在第一次回调后删除观察者
Posted
技术标签:
【中文标题】LiveData 在第一次回调后删除观察者【英文标题】:LiveData remove Observer after first callback 【发布时间】:2018-05-30 23:27:38 【问题描述】:收到第一个结果后如何移除观察者?以下是我尝试过的两种代码方式,但即使我删除了观察者,它们都会继续接收更新。
Observer observer = new Observer<DownloadItem>()
@Override
public void onChanged(@Nullable DownloadItem downloadItem)
if(downloadItem!= null)
DownloadManager.this.downloadManagerListener.onDownloadManagerFailed(null, "this item already exists");
return;
startDownload();
model.getDownloadByContentId(contentId).removeObservers((AppCompatActivity)context);
;
model.getDownloadByContentId(contentId).observeForever(observer);
model.getDownloadByContentId(contentId).observe((AppCompatActivity)context, downloadItem->
if(downloadItem!= null)
this.downloadManagerListener.onDownloadManagerFailed(null, "this item already exists");
return;
startDownload();
model.getDownloadByContentId(contentId).removeObserver(downloadItem-> );
);
【问题讨论】:
【参考方案1】:有一个更方便的 Kotlin 扩展解决方案:
fun <T> LiveData<T>.observeOnce(lifecycleOwner: LifecycleOwner, observer: Observer<T>)
observe(lifecycleOwner, object : Observer<T>
override fun onChanged(t: T?)
observer.onChanged(t)
removeObserver(this)
)
这个扩展允许我们这样做:
liveData.observeOnce(this, Observer<Password>
if (it != null)
// do something
)
所以要回答你原来的问题,我们可以这样做:
val livedata = model.getDownloadByContentId(contentId)
livedata.observeOnce((AppCompatActivity) context, Observer<T>
if (it != null)
DownloadManager.this.downloadManagerListener.onDownloadManagerFailed(null, "this item already exists");
startDownload();
)
原文出处:https://code.luasoftware.com/tutorials/android/android-livedata-observe-once-only-kotlin/
更新:@Hakem-Zaied 是对的,我们需要使用 observe
而不是 observeForever
。
【讨论】:
我可以建议先删除观察者 - 只有然后调用用户的onChanged
。否则,如果用户的实现抛出异常,中间观察者将“永远”保持注册状态。
如果在哪里声明扩展函数不明显,请参阅:kotlinlang.org/docs/reference/…
很棒的答案!我稍微修改了一下,现在它更像 Kotlinish 了! gist.github.com/bartekpacia/eb1c92886acf3972c3f030cde2579ebb
这段代码有问题。您需要添加检查以查看生命周期所有者是否已销毁,否则可能会导致泄漏和尝试调用已销毁片段的方法或属性的问题。您应该编辑它并将其添加到 onChanged: if (owner.lifecycle.currentState == Lifecycle.State.DESTROYED) removeObserver(this) return
@paul_f 我不这么认为,如果所有者已经被销毁,那么observe
将忽略该调用。查看来源here【参考方案2】:
您的第一个将不起作用,因为observeForever()
未绑定到任何LifecycleOwner
。
您的第二个将不起作用,因为您没有将现有的注册观察者传递给removeObserver()
。
您首先需要确定是否将LiveData
与LifecycleOwner
(您的活动)一起使用。我的假设是您应该使用LifecycleOwner
。在这种情况下,请使用:
Observer observer = new Observer<DownloadItem>()
@Override
public void onChanged(@Nullable DownloadItem downloadItem)
if(downloadItem!= null)
DownloadManager.this.downloadManagerListener.onDownloadManagerFailed(null, "this item already exists");
return;
startDownload();
model.getDownloadByContentId(contentId).removeObservers((AppCompatActivity)context);
;
model.getDownloadByContentId(contentId).observe((AppCompatActivity)context, observer);
【讨论】:
这将从活动/片段中删除所有观察者。我们如何才能只删除当前的观察者,而不是全部? @MrVasilev:我不知道“当前”在这种情况下是什么意思。要删除单个观察者,请在LiveData
上调用 removeObserver()
。
@CommonsWare 对不起,如果我不清楚。当我尝试这样做时: var liveData = viewModel.findNearestDriver(location) liveData.observe(this, Observer liveData.removeObserver(this) ) 我收到“类型不匹配”编译错误,因为这不是我的观察者,而是我的片段
@MrVasilev:这可能是 Kotlin SAM 对 lambdas 或其他东西的支持的问题。您可能需要使用object : Observer
并创建一个“真实的”Observer
实例,以便从Observer
的onChanged()
函数内部获取正确的this
。
@CommonsWare 感谢您的回复,您说得对,这是 Kotlin 的问题。只需提一下,对于 Kotlin,解决方案可能是像这样创建“LiveData”的扩展: fun 按照CommonsWare 的回答,您可以简单地调用removeObserver(this)
来仅删除该观察者,而不是调用removeObservers()
来删除所有附加到LiveData 的观察者:
Observer observer = new Observer<DownloadItem>()
@Override
public void onChanged(@Nullable DownloadItem downloadItem)
if(downloadItem!= null)
DownloadManager.this.downloadManagerListener.onDownloadManagerFailed(null, "this item already exists");
return;
startDownload();
model.getDownloadByContentId(contentId).removeObserver(this);
;
model.getDownloadByContentId(contentId).observe((AppCompatActivity)context, observer);
注意: 在removeObserver(this)
中,this
指的是观察者实例,这仅适用于匿名内部类的情况。如果您使用 lambda,则 this
将引用活动实例。
【讨论】:
这里要小心...根据您的查询返回的速度,这实际上会导致一个无限循环,其中 model.myThing 在调用 removeObserver 之前返回(我遇到过这种情况)。 @Psest328 这究竟是如何导致循环的? 我说LiveData
似乎没有没有参数的方法removeObservers()
是不是错了?在不知道生命周期所有者的情况下,我似乎无法找到删除所有观察者的方法。
@Nick 您正在调用该方法并在调用删除观察者的过程中将其启动。如果该过程在观察者被移除之前完成,则会导致无限循环。你基本上是在创造一个竞争条件
@Psest328 "观察者被移除之前" 观察者被同步移除...【参考方案4】:
我喜欢 Vince 和 Hakem Zaied 的通用解决方案,但对我来说,lambda 版本似乎更好:
fun <T> LiveData<T>.observeOnce(observer: (T) -> Unit)
observeForever(object: Observer<T>
override fun onChanged(value: T)
removeObserver(this)
observer(value)
)
fun <T> LiveData<T>.observeOnce(owner: LifecycleOwner, observer: (T) -> Unit)
observe(owner, object: Observer<T>
override fun onChanged(value: T)
removeObserver(this)
observer(value)
)
所以你最终得到:
val livedata = model.getDownloadByContentId(contentId)
livedata.observeOnce((AppCompatActivity) context)
if (it != null)
DownloadManager.this.downloadManagerListener.onDownloadManagerFailed(null, "this item already exists")
startDownload();
我觉得更干净。
另外,removeObserver()
在观察者被调度时被称为第一件事,这使得它更安全(即处理用户观察者代码中潜在的运行时错误抛出)。
【讨论】:
Kotlin 用户的完美答案!我只是写了相同的代码并想发布它,直到我找到它。 +1 这段代码有问题。您需要添加检查以查看生命周期所有者是否已销毁,否则可能会导致泄漏和尝试调用已销毁片段的方法或属性的问题。您应该编辑它并将其添加到 onChanged: if (owner.lifecycle.currentState == Lifecycle.State.DESTROYED) removeObserver(this) return 如果我在 oncreateview 中使用它,它会在我回到片段时触发,有什么建议我应该在哪个生命周期方法中使用它? 谢谢。这很好地缓解了问题。【参考方案5】:我同意上面的Vince,但我相信我们要么跳过lifecycleOwner
,而是使用observerForever
,如下所示:
fun <T> LiveData<T>.observeOnce(observer: Observer<T>)
observeForever(object : Observer<T>
override fun onChanged(t: T?)
observer.onChanged(t)
removeObserver(this)
)
或者,使用lifecycleOwner
和observe
如下:
fun <T> LiveData<T>.observeOnce(lifecycleOwner: LifecycleOwner, observer: Observer<T>)
observe(lifecycleOwner, object : Observer<T>
override fun onChanged(t: T?)
observer.onChanged(t)
removeObserver(this)
)
【讨论】:
您可以使用 Java 发布相同的答案吗?我对 Kotlin 的了解为零。 这段代码有问题。您需要添加检查以查看生命周期所有者是否已销毁,否则可能会导致泄漏和尝试调用已销毁片段的方法或属性的问题。您应该编辑它并将其添加到 onChanged: if (owner.lifecycle.currentState == Lifecycle.State.DESTROYED) removeObserver(this) return 谢谢,它有效,但在所有情况下都取消订阅。例如,您加载一个列表,具有三种状态:加载、成功、错误。然后你只会看到加载状态。要修复它,请在完成状态(成功、错误)后取消订阅。【参考方案6】:这是其他答案中建议的 observeOnce
方法的 Java 版本(一个 util 类方法而不是 Kotlin 扩展函数):
public class LiveDataUtil
public static <T> void observeOnce(final LiveData<T> liveData, final Observer<T> observer)
liveData.observeForever(new Observer<T>()
@Override
public void onChanged(T t)
liveData.removeObserver(this);
observer.onChanged(t);
);
【讨论】:
【参考方案7】:Java 版本的observeOnce
方法已经被许多用户推荐。但这里我们将在主代码中实现。
首先,我们需要创建Util类方法
public class LiveDataUtil
public static <T> void observeOnce(final LiveData<T> liveData, final Observer<T> observer)
liveData.observeForever(new Observer<T>()
@Override
public void onChanged(T t)
liveData.removeObserver(this);
observer.onChanged(t);
);
现在,我们需要在需要 ViewModel 的地方调用这个类。
LiveDataUtil.observeOnce(viewModel.getUserDetails(), response->
if(response.isSuccessful())
//Do your task
就是这样!
【讨论】:
【参考方案8】:您不止一次地创建实时数据实例 (model.getDownloadByContentId(contentId)),这是这里的问题。
试试这个:
LiveData myLiveData =model.getDownloadByContentId(contentId);
myLiveData.observe(getViewLifecycleOwner(), downloadItem->
if(downloadItem!= null)
this.downloadManagerListener.onDownloadManagerFailed(null, "this item already exists");
return;
startDownload();
myLiveData.removeObservers(getViewLifecycleOwner());
);
【讨论】:
【参考方案9】:@CommonsWare 和@Toni Joe 提出的解决方案没有为我解决问题,因为我在 ViewModel 中收到 DAO 查询的第一个结果后需要删除观察者。但是,在Livedata keeps observer after calling removeObserer 找到的以下解决方案以我自己的一点直觉为我解决了问题。
过程如下,根据请求在存储LiveData的ViewModel中创建一个变量,在进行null检查后在activity中的create observer函数调用中检索它,并在调用flushToDB之前调用remove observers函数导入类中的例程。也就是说,我的 ViewModel 中的代码如下所示:
public class GameDataModel extends AndroidViewModel
private LiveData<Integer> lastMatchNum = null;
.
.
.
private void initLastMatchNum(Integer player1ID, Integer player2ID)
List<Integer> playerIDs = new ArrayList<>();
playerIDs.add(player1ID);
playerIDs.add(player2ID);
lastMatchNum = mRepository.getLastMatchNum(playerIDs);
public LiveData<Integer> getLastMatchNum(Integer player1ID, Integer player2ID)
if (lastMatchNum == null) initLastMatchNum(player1ID, player2ID);
return lastMatchNum;
在上面,如果 ViewModel 中的 LiveData 变量中没有数据,我会调用 initLastMatchNum()
从视图模型中的函数中检索数据。要从活动中调用的函数是getLastMatchNum()
。该例程检索 ViewModel 中变量中的数据(通过 DAO 通过存储库检索)。
我的活动中有以下代码
public class SomeActivity extends AppCompatActivity
@Override
protected void onCreate(Bundle savedInstanceState)
.
.
.
setupLastMatchNumObserver();
.
.
.
private void setupLastMatchNumObserver()
if (mGameDataViewModel.getLastMatchNum(Player1ID, Player2ID).hasObservers())
Log.v("Observers", "setupLastMatchNumObserver has observers...returning");
return;
Log.v("Setting up Observers", "running mGameDataViewModel.get...observer()");
mGameDataViewModel.getLastMatchNum(Player1ID, Player2ID).observe(this, new Observer<Integer>()
@Override
public void onChanged(Integer MatchNumber)
if (MatchNumber == null )
matchNumber = 1;
Log.v( "null MatchNumber", "matchNumber: " + matchNumber.toString());
else
matchNumber = MatchNumber; matchNumber++;
Log.v( "matchNumber", "Incrementing match number: " + matchNumber.toString());
MatchNumberText.setText(matchNumber.toString());
);
private void removeObservers()
final LiveData<Integer> observable = mGameDataViewModel.getLastMatchNum(Player1ID, Player2ID);
if (observable != null && observable.hasObservers())
Log.v("removeObserver", "Removing Observers");
observable.removeObservers(this);
上面发生的事情是 1.) 我在活动的onCreate
方法中调用setupLastMatchNumObserver()
例程,以更新类的变量matchNum
。这会跟踪存储在数据库中的游戏中玩家之间的匹配数。每组球员在数据库中都会有不同的比赛号码,这取决于他们彼此进行新比赛的频率。这个线程中的第一个解决方案对我来说似乎有点厌倦,因为在onChanged
中调用删除观察者对我来说似乎很奇怪,并且会在玩家每次移动的每次数据库刷新后不断更改TextView
对象。所以matchNumber
在每次移动后都会增加,因为在第一次移动之后数据库中有一个新值(即matchNumber++
值),并且onChanged
一直被调用,因为removeObservers
没有按预期工作。 setupLastMatchNumObserver()
检查是否有实时数据的观察者,如果有,则不会在每一轮实例化一个新调用。如您所见,我正在设置一个TextView
对象来反映玩家的当前比赛编号。
下一部分是关于何时调用removeObservers()
的小技巧。起初我想如果我在 setupLastMatchNumObserver()
之后直接调用它,在活动的 onCreate
覆盖中,一切都会好起来的。但它在观察者获取数据之前移除了观察者。我发现如果我在调用之前直接调用removeObservers()
将活动中收集的新数据刷新到数据库(在整个活动中的单独例程中),它就像一个魅力。即,
public void addListenerOnButton()
.
.
.
@Override
public void onClick(View v)
.
.
.
removeObservers();
updateMatchData(data);
我也以上述方式在我的活动中的其他地方调用removeObservers();
和updateMatchData(data)
。美妙之处在于removeObservers()
可以根据需要多次调用,因为如果没有观察者在场,则会返回检查。
【讨论】:
【参考方案10】:-
LiveData 类有 2 个类似的方法来删除观察者。首先是命名,
removeObserver(@NonNull final Observer<T> observer)
(仔细看方法的名称,它是单数的)它接收你想要从同一个 LifecycleOwner 的观察者列表中删除的观察者。
-
第二种方法是
removeObservers(@NonNull final LifecycleOwner owner)
(见复数方法名)。该方法接受 LifecycleOwner 本身,并移除指定 LifecycleOwner 的所有 Observer。
现在,在您的情况下,您可以通过 2 种方式删除您的观察者(可能有多种方式),@ToniJoe 在上一个答案中告诉您。
另一种方法是在您的 ViewModel 中添加一个 MutableLiveData 布尔值,它在第一次被观察时存储 true,并且也只观察该 Livedata。因此,只要它变为 true,您就会收到通知,并且您可以通过传递特定的观察者来移除您的观察者。
【讨论】:
【参考方案11】:Vince 和 Hakem Zaied 解决方案运行良好,但在我的情况下,我试图获取 livedata 实例并更新本地数据库,但 livedata 将首先从远程 API 更新,因此我得到了 NullPointer,所以我切换到observeForever,我能够在更新数据时获取数据,但现在我必须在获取数据后处理观察者,所以我修改了Vince解决方案,只在livedata包含数据时观察和发出数据。
fun <T> LiveData<T>.observeOnce(observer: (T) -> Unit)
observeForever(object : Observer<T>
override fun onChanged(value: T)
//Resource is my data class response wrapper, with this i was able to
//only update the observer when the livedata had values
//the idea is to cast the value to the expected type and check for nulls
val resource = value as Resource<*>
if (resource.data != null)
observer(value)
removeObserver(this)
)
【讨论】:
【参考方案12】:这是一个 androidx.lifecycle.Observer Java 示例:
Observer <? super List<MyEntity>> observer = new Observer<List<MyEntity>>()
@Override
public void onChanged(List<MyEntity> myEntities)
Log.d(TAG, "observer changed");
MySearchViewModel.getMyList().removeObserver(this);
;
MySearchViewModel.getMyList().observe(MainActivity.this, observer);
【讨论】:
【参考方案13】:在我看来,Livedata 旨在持续接收即将到来的数据。如果您只是希望它只执行一次,例如从服务器请求数据以初始化 UI,我建议您以这种方式设计代码:
1、将您的耗时方法定义为 Viewmodel 中的 non-Livedata 类型。您不必在此过程中启动新线程。
2、在一个Activity中启动一个new Thread,在新的Thread中,调用上面定义的方法,然后是runOnUiThread()
,你可以在其中写下你使用请求数据的逻辑。这样耗时的方法不会阻塞 UI 线程,而是阻塞新线程,所以 runOnUiThread() 仅在您请求的数据成功接收后运行。
因此,如果这是您想要的,请考虑更换 Livedata。
【讨论】:
以上是关于LiveData 在第一次回调后删除观察者的主要内容,如果未能解决你的问题,请参考以下文章
为啥用 viewLifecycleOwner 观察到的 LiveData 在 onDestroyView 之后会得到回调?