订阅的结果未被使用

157

今天我升级了Android Studio 3.1,它似乎增加了一些严格检查。其中一个检查是针对未存储在变量中的一次性RxJava2subscribe()调用的。例如,从我的Room数据库获取所有玩家列表:

Single.just(db)
            .subscribeOn(Schedulers.io())
            .subscribe(db -> db.playerDao().getAll());

一个大黄色块的结果和这个提示:

subscribe 的结果未被使用

Android Studio 的截图,代码用黄色高亮显示并带有工具提示。工具提示文本:The result of subscribe is not used.

对于类似这样的一次性 Rx 调用,最佳实践是什么?我应该保留 Disposabledispose() 完成请求吗?还是只需要 @SuppressLint 然后继续进行下一步操作?

这只影响 RxJava2 (io.reactivex),RxJava (rx) 没有此 Lint。


1
在你提供的两个解决方案中,我真诚地认为@SuppressLint不是最好的选择。也许我错了,但我真的认为代码不应该改变IDE警告和/或提示。 - Arthur Attout
@ArthurAttout 同意,目前我将 Disposable 保留在成员范围内,并在单个完成时调用 dispose(),但这似乎过于繁琐。我很想看看是否有更好的方法来解决这个问题。 - Michael Dodd
8
当 RxJava 流不是从 Activity/Fragment/ViewModel 中订阅时,我认为这个 lint 警告很烦人。我有一个 Completable,它可以安全地在不考虑 Activity 生命周期的情况下运行,但我仍然需要处理它的清理? - E.M.
请考虑使用RxLifecycle。 - 최봉재
8个回答

155

当订阅未被处理时,IDE不知道它可能产生的影响,因此将其视为潜在的不安全因素。例如,您的Single可能包含网络调用,在执行过程中如果您的Activity被丢弃,则可能导致内存泄漏。

管理大量Disposable的便捷方法是使用CompositeDisposable;只需在封闭类中创建一个新的CompositeDisposable实例变量,然后将所有Disposables添加到CompositeDisposable中(使用RxKotlin,您可以将addTo(compositeDisposable)附加到所有Disposables)。最后,当您完成实例时,请调用compositeDisposable.dispose()

这将消除lint警告,并确保正确管理Disposables

在这种情况下,代码将如下所示:

CompositeDisposable compositeDisposable = new CompositeDisposable();

Disposable disposable = Single.just(db)
        .subscribeOn(Schedulers.io())
        .subscribe(db -> db.get(1)));

compositeDisposable.add(disposable); //IDE is satisfied that the Disposable is being managed. 
disposable.addTo(compositeDisposable); //Alternatively, use this RxKotlin extension function.


compositeDisposable.dispose(); //Placed wherever we'd like to dispose our Disposables (i.e. in onDestroy()).

2
是的,这是一个 RxKotlin 方法。 - urgentx
1
在出现可流动的情况下该怎么办 - Hunt
如果我们在 doOnSubscribe 中执行这个操作,该怎么办? - Shubham AgaRwal
3
不会造成内存泄漏。一旦网络调用完成并调用了onComplete,垃圾回收机制将处理其余部分,除非您保留了对可释放对象的显式引用并且没有对其进行处理。 - Gabriel Vasconcelos
@GabrielVasconcelos 如果网络调用永远不会完成怎么办?或者将上下文传递到其他地方?我们需要通知上游生产者观察已经完成,并释放我们传递给他们的任何资源。 - urgentx
显示剩余2条评论

27

当Activity被销毁时,Disposables列表被清空,这样就行了。

io.reactivex.disposables.CompositeDisposable mDisposable;

    mDisposable = new CompositeDisposable();

    mDisposable.add(
            Single.just(db)
                    .subscribeOn(Schedulers.io())
                    .subscribe(db -> db.get(1)));

    mDisposable.dispose(); // dispose wherever is required

16

您可以使用DisposableSingleObserver进行订阅:

Single.just(db)
    .subscribeOn(Schedulers.io())
    .subscribe(new DisposableSingleObserver<Object>() {
            @Override
            public void onSuccess(Object obj) {
                // work with the resulting todos...
                dispose();
            }

            @Override
            public void onError(Throwable e) {
                // handle the error case...
                dispose();
            }});

如果需要直接处理Single对象(例如在其发出之前),您可以实现onSubscribe(Disposable d)方法以获取并使用Disposable引用。

您还可以自己实现SingleObserver接口或使用其他子类。


5

建议您可以使用全局 CompositeDisposable 将 subscribe 操作的结果添加到其中。

RxJava2Extensions 库包含有用的方法,可自动从 CompositeDisposable 中删除创建的 disposable(一次性资源) when it completes。请参见 subscribeAutoDispose 章节。

在您的情况下,可以像这样:

SingleConsumers.subscribeAutoDispose(
    Single.just(db)
            .subscribeOn(Schedulers.io()),
    composite,
    db -> db.playerDao().getAll())

2
一次又一次地,我发现自己回到了如何正确处理订阅的问题,并且特别是这篇文章。几个博客和演讲声称,未调用dispose会导致内存泄漏,我认为这是一个太笼统的说法。在我看来,有关不存储subscribe结果的lint警告在某些情况下并不重要,因为:
  • 并非所有可观察对象都在Android活动的上下文中运行
  • 可观察对象可以是同步的
  • 只要可观察对象完成,就会隐式调用Dispose

由于我不想抑制lint警告,所以最近开始在同步可观察对象的情况下使用以下模式:

var disposable: Disposable? = null

disposable = Observable
   .just(/* Whatever */)
   .anyOperator()
   .anyOtherOperator()
   .subscribe(
      { /* onSuccess */ },
      { /* onError */ },
      {
         // onComplete
         // Make lint happy. It's already disposed because the stream completed.
         disposable?.dispose()
      }
   )

无论是对正确性的确认还是漏洞的发现,我都很希望听到您的评论关于这个问题。


2
您可以使用 Uber AutoDispose 和 rxjava .as
        Single.just(db)
            .subscribeOn(Schedulers.io())
            .as(AutoDispose.autoDisposable(AndroidLifecycleScopeProvider.from(this)))
            .subscribe(db -> db.playerDao().getAll());

确保您理解在 ScopeProvider 基础上取消订阅的时机。

假定已经有一个生命周期提供者。另外,“as”方法被标记为不稳定,使用它将导致 Lint 警告。 - Dabbler
1
感谢@Dabbler,我同意。 .as 方法在 RxJava 2.1.7 之前是实验性的,在 2.2 版本中已经稳定下来了。 - blaffie

0

还有另一种可用的方法,就是避免手动使用Disposables(添加和删除订阅)。

您可以定义一个Observable,该Observable将从SubjectBehaviour(如果您使用RxJava)接收内容。通过将该observable传递给您的LiveData,应该可以解决问题。请查看以下基于初始问题的示例:

private val playerSubject: Subject<Player> = BehaviorSubject.create()

private fun getPlayer(idPlayer: String) {
        playerSubject.onNext(idPlayer)
}

private val playerSuccessful: Observable<DataResult<Player>> = playerSubject
                        .flatMap { playerId ->
                            playerRepository.getPlayer(playerId).toObservable()
                        }
                        .share()

val playerFound: LiveData<Player>
    get() = playerSuccessful
        .filterAndMapDataSuccess()
        .toLiveData()

val playerNotFound: LiveData<Unit>
    get() = playerSuccessful.filterAndMapDataFailure()
        .map { Unit }
        .toLiveData()

// These are a couple of helpful extensions

fun <T> Observable<DataResult<T>>.filterAndMapDataSuccess(): Observable<T> =
filter { it is DataResult.Success }.map { (it as DataResult.Success).data }

fun <T> Observable<DataResult<T>>.filterAndMapDataFailure(): Observable<DataResult.Failure<T>> =
filter { it is DataResult.Failure }.map { it as DataResult.Failure<T> }

-13

如果您确定已正确处理一次性操作,例如使用doOnSubscribe()运算符,则可以将以下内容添加到Gradle中:

android {
lintOptions {
     disable 'CheckResult'
}}

11
这将抑制所有未经检查结果实例的此代码检查。在OP示例之外有很多时候,某人应该处理返回的结果。这就像用大锤子杀蚊子。 - tir38
17
请不要这样做!你收到这些警告是有原因的。如果你知道自己在做什么(并且确定你真的不需要取消订阅),你可以在方法上使用@SuppressLint("CheckResult")来抑制警告。 - Victor Rendina

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接