Результат подписки не используется

133

Сегодня я обновился до Android Studio 3.1, который, кажется, добавил еще несколько проверок на ворсинок. Одна из этих проверок линта предназначена для однократных subscribe()вызовов RxJava2 , которые не хранятся в переменной. Например, получение списка всех игроков из базы данных моей комнаты:

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

В результате появится большой желтый блок и всплывающая подсказка:

Результат subscribeне используется

Скриншот Android Studio.  Код выделен желтым цветом с всплывающей подсказкой.  Текст всплывающей подсказки: результат подписки не используется.

Какова наилучшая практика для таких однократных вызовов Rx? Должен ли я придерживаться Disposableи dispose()по полной? Или мне просто нужно @SuppressLintдвигаться дальше?

Кажется, это влияет только на RxJava2 ( io.reactivex), RxJava ( rx) не имеет этого линта.

Майкл Додд
источник
Из обоих ваших решений я честно думаю, что @SuppressLint не самый лучший. Возможно, я ошибаюсь, но я действительно думаю, что код никогда не должен изменять предупреждения и / или подсказки IDE
Артур Аттаут,
@ArthurAttout Согласен, в настоящее время я сохраняю Disposableобласть действия члена и вызываю ее, dispose()когда сингл завершается, но это кажется излишне громоздким. Мне интересно узнать, есть ли способы сделать это лучше.
Майкл Додд,
8
Я думаю, что это предупреждение о линтах раздражает, когда поток RxJava не подписан из Activity / Fragment / ViewModel. У меня есть Completable, который можно безопасно запускать, не обращая внимания на жизненный цикл Activity, но мне все равно нужно от него избавиться?
EM
рассмотрите RxLifecycle
최봉재

Ответы:

123

IDE не знает, какие потенциальные последствия может иметь ваша подписка, если она не удалена, поэтому она рассматривает ее как потенциально опасную. Например, ваш Singleможет содержать сетевой вызов, который может вызвать утечку памяти, если вас Activityбросят во время его выполнения.

Удобный способ управлять большим количеством Disposables - использовать CompositeDisposable ; просто создайте новую CompositeDisposableпеременную экземпляра в вашем включающем классе, затем добавьте все свои Disposables в CompositeDisposable (с RxKotlin вы можете просто добавить addTo(compositeDisposable)ко всем своим Disposable). Наконец, когда вы закончите со своим экземпляром, позвоните compositeDisposable.dispose().

Это избавит от предупреждений о ворсинках и обеспечит 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()).
urgentx
источник
Я получаю ошибку компиляции error: cannot find symbol method addTo(CompositeDisposable)с "rxjava: 2.1.13". Откуда этот метод? (RxSwift или RxKotlin, я полагаю)
aeracode
2
Да, это метод RxKotlin.
actualx
1
что делать в случае текучести
Hunt
Что, если мы делаем это в doOnSubscribe
Killer
2
Это не вызовет утечки памяти. После завершения сетевого вызова и вызова onComplete сборка мусора выполнит все остальное, если только вы не сохранили явную ссылку на одноразовый объект и не удалили его.
Габриэль Васконселос
26

В тот момент, когда 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
Aks4125
источник
10

Вы можете подписаться с 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интерфейс самостоятельно или использовать другие дочерние классы.

papandreus
источник
5

Как было предложено, вы можете использовать некоторые глобальные данные, CompositeDisposableчтобы добавить туда результат операции подписки.

Библиотека RxJava2Extensions содержит полезные методы для автоматического удаления созданного одноразового использования из файлаCompositeDisposable после его завершения. См. Раздел subscribeAutoDispose .

В вашем случае это может выглядеть так

SingleConsumers.subscribeAutoDispose(
    Single.just(db)
            .subscribeOn(Schedulers.io()),
    composite,
    db -> db.playerDao().getAll())
Евгений Попович
источник
2

Вы можете использовать Uber AutoDispose и rxjava.as

        Single.just(db)
            .subscribeOn(Schedulers.io())
            .as(AutoDispose.autoDisposable(AndroidLifecycleScopeProvider.from(this)))
            .subscribe(db -> db.playerDao().getAll());

Убедитесь, что вы понимаете, когда отказываетесь от подписки на основе ScopeProvider.

blaffie
источник
Это предполагает наличие поставщика жизненного цикла. Кроме того, метод «as» помечен как нестабильный, поэтому его использование приведет к предупреждению Lint.
Dabbler
1
Спасибо @Dabbler, согласился. Метод .as был экспериментальным до RxJava 2.1.7, а в версии 2.2 он стабилен.
blaffie
1

Снова и снова я возвращаюсь к вопросу о том, как правильно распоряжаться подписками и, в частности, с этой публикацией. В нескольких блогах и в беседах утверждается, что отказ от вызова disposeобязательно ведет к утечке памяти, что, на мой взгляд, является слишком общим утверждением. Насколько я понимаю, предупреждение о subscribeнесохранении результата не является проблемой в некоторых случаях, потому что:

  • Не все наблюдаемые работают в контексте активности Android.
  • Наблюдаемое может быть синхронным
  • Dispose вызывается неявно, если наблюдаемый завершается

Поскольку я не хочу подавлять предупреждения о ворсинах, я недавно начал использовать следующий шаблон для случаев с синхронным наблюдаемым:

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()
      }
   )

Мне были бы интересны любые комментарии по этому поводу, независимо от того, является ли это подтверждением правильности или обнаружением лазейки.

дилетант
источник
0

Доступен еще один способ, который позволяет избежать использования Disposables вручную (добавлять и удалять подписки).

Вы можете определить Observable, и этот Observable будет получать контент от SubjectBehaviour (если вы используете RxJava). И если передать это наблюдаемое в 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> }
Фернандо Прието
источник
-10

Если вы уверены, что одноразовые предметы обрабатываются правильно, например, используя оператор doOnSubscribe (), вы можете добавить это в Gradle:

android {
lintOptions {
     disable 'CheckResult'
}}
Иван
источник
10
Это подавит эту проверку ворса для всех случаев непроверенного результата. За пределами примера OP существует множество случаев, когда кто-то должен обрабатывать возвращенный результат. Это использование кувалды, чтобы убить муху.
tir38
16
Пожалуйста, не делай этого! Есть причина, по которой вы получаете эти предупреждения. Если вы знаете, что делаете (и знаете, что вам действительно не нужно избавляться от подписки), вы можете подавить с помощью @SuppressLint("CheckResult")только метода.
Victor Rendina