Wynik subskrypcji nie jest używany

136

Zaktualizowałem dzisiaj do Android Studio 3.1, który wydaje się mieć kilka dodatkowych sprawdzeń kłaczków. Jedno z tych sprawdzeń lintów dotyczy jednorazowych subscribe()wywołań RxJava2 , które nie są przechowywane w zmiennej. Na przykład, pobieranie listy wszystkich graczy z mojej bazy danych Pokoju:

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

W rezultacie powstaje duży żółty blok i ta podpowiedź:

Wynik subscribenie jest używany

Zrzut ekranu z Android Studio.  Kod jest podświetlony na żółto wraz z etykietką.  Tekst podpowiedzi: wynik subskrypcji nie jest używany.

Jaka jest najlepsza praktyka w przypadku takich jednorazowych wywołań Rx? Czy powinienem zachować kompletność Disposablei dispose()na? A może powinienem po prostu @SuppressLintiść dalej?

Wydaje się, że dotyczy to tylko RxJava2 ( io.reactivex), RxJava ( rx) nie ma tego linta .

Michael Dodd
źródło
Z obu twoich rozwiązań, szczerze myślę, że @SuppressLint nie jest najlepszym. Może się mylę, ale naprawdę uważam, że kod nigdy nie powinien zmieniać ostrzeżeń i / lub wskazówek IDE
Arthur Attout
@ArthurAttout Zgoda, obecnie trzymam Disposablezakres członków at i wzywam, dispose()gdy singiel się zakończy, ale wydaje się to niepotrzebnie kłopotliwe. Jestem zainteresowany, aby sprawdzić, czy są na to lepsze sposoby.
Michael Dodd,
8
Myślę, że to ostrzeżenie o kłaczkach jest denerwujące, gdy strumień RxJava nie jest subskrybowany z poziomu Activity / Fragment / ViewModel. Mam element Completable, który można bezpiecznie uruchomić bez względu na cykl życia działania, ale nadal muszę go pozbyć?
EM
rozważ RxLifecycle
최봉재

Odpowiedzi:

126

Środowisko IDE nie wie, jakie potencjalne skutki może mieć Twoja subskrypcja, gdy nie zostanie wyrzucona, więc traktuje ją jako potencjalnie niebezpieczną. Na przykład Singlemoże zawierać wywołanie sieciowe, które może spowodować wyciek pamięci, jeśli Activityzostanie porzucone podczas wykonywania.

Wygodnym sposobem zarządzania dużą liczbą Disposables jest użycie CompositeDisposable ; po prostu utwórz nową CompositeDisposablezmienną instancji w klasie otaczającej, a następnie dodaj wszystkie swoje produkty jednorazowe do CompositeDisposable (za pomocą RxKotlin możesz po prostu dołączyć addTo(compositeDisposable)do wszystkich swoich produktów jednorazowych). Wreszcie, kiedy skończysz z instancją, zadzwoń compositeDisposable.dispose().

Pozwoli to usunąć ostrzeżenia o kłaczkach i zapewni Disposablesprawidłowe zarządzanie.

W tym przypadku kod wyglądałby tak:

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()).
pilnex
źródło
Otrzymuję błąd kompilacji error: cannot find symbol method addTo(CompositeDisposable)z „rxjava: 2.1.13”. Skąd ta metoda? (Przypuszczam, że RxSwift lub RxKotlin)
aeracode
2
Tak, to metoda RxKotlin.
urgentx
1
co zrobić w przypadku płynnego materiału
Hunt
A co jeśli robimy to w doOnSubscribe
Killer
2
Nie spowodowałoby to wycieku pamięci. Po zakończeniu wywołania sieciowego i wywołaniu funkcji onComplete, proces czyszczenia pamięci zajmie się resztą, chyba że zachowałeś jawne odwołanie do elementu jednorazowego użytku i nie wyrzucasz go.
Gabriel Vasconcelos
26

W momencie zniszczenia Aktywności lista rzeczy jednorazowego użytku zostaje wyczyszczona i wszystko jest w porządku.

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
źródło
11

Możesz subskrybować za pomocą 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();
            }});

W przypadku, gdy chcesz bezpośrednio pozbyć się Singleobiektu (np. Zanim wyemituje), możesz zaimplementować metodę, onSubscribe(Disposable d)aby uzyskać i użyć Disposablereferencji.

Możesz również zrealizować SingleObserverinterfejs samodzielnie lub użyć innych klas podrzędnych.

papandreus
źródło
5

Jak sugerowano, możesz użyć globalnego, CompositeDisposableaby dodać tam wynik operacji subskrypcji.

RxJava2Extensions biblioteka zawiera użyteczne sposoby, aby automatycznie usunąć utworzone jednorazowe od CompositeDisposablekiedy to kończy. Zobacz sekcję subscribeAutoDispose .

W twoim przypadku może to wyglądać tak

SingleConsumers.subscribeAutoDispose(
    Single.just(db)
            .subscribeOn(Schedulers.io()),
    composite,
    db -> db.playerDao().getAll())
Eugene Popovich
źródło
2

Możesz użyć Uber AutoDispose i rxjava.as

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

Upewnij się, że rozumiesz, kiedy rezygnujesz z subskrypcji na podstawie ScopeProvider.

blaffie
źródło
Zakłada się, że dostępny jest dostawca cyklu życia. Ponadto metoda „as” jest oznaczona jako niestabilna, więc jej użycie spowoduje wyświetlenie ostrzeżenia Lint.
Dabbler
1
Dzięki @Dabbler, zgodził się. Metoda .as była eksperymentalna do wersji RxJava 2.1.7, a od wersji 2.2 jest stabilna.
blaffie
0

Ciągle wracam do pytania, jak prawidłowo pozbyć się subskrypcji, aw szczególności do tego postu. Kilka blogów i wykładów twierdzi, że nieudane wezwanie disposeprowadzi do wycieku pamięci, co moim zdaniem jest zbyt ogólne. W moim rozumieniu ostrzeżenie o subscribenieprzechowywaniu wyniku w niektórych przypadkach nie stanowi problemu, ponieważ:

  • Nie wszystkie obserwowalne działają w kontekście działania systemu Android
  • Obserwowalne może być synchroniczne
  • Dispose jest wywoływana niejawnie, pod warunkiem, że obserwowalne zostaną zakończone

Ponieważ nie chcę tłumić ostrzeżeń o kłaczkach, ostatnio zacząłem używać następującego wzorca dla przypadków z obserwowalnym synchronicznym:

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

Byłbym zainteresowany wszelkimi komentarzami na ten temat, niezależnie od tego, czy jest to potwierdzenie poprawności, czy odkrycie luki.

Amator
źródło
0

Istnieje inny sposób, który polega na unikaniu ręcznego używania produktów jednorazowych (dodawanie i usuwanie subskrypcji).

Możesz zdefiniować Observable, który otrzyma zawartość z SubjectBehaviour (w przypadku korzystania z RxJava). I przekazując to obserwowalne do LiveData , powinno to działać. Sprawdź następny przykład oparty na pierwszym pytaniu:

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> }
Fernando Prieto
źródło
-11

Jeśli jesteś pewien, że disposable obsługiwane poprawnie, na przykład za pomocą operatora doOnSubscribe (), możesz dodać to do Gradle:

android {
lintOptions {
     disable 'CheckResult'
}}
Ivan
źródło
10
Spowoduje to pominięcie tego sprawdzania kłaczków dla wszystkich wystąpień niesprawdzonego wyniku. Wiele razy poza przykładem OP ktoś powinien zająć się zwróconym wynikiem. To jest użycie młota do zabicia muchy.
tir38
16
Proszę, nie rób tego! Jest powód, dla którego otrzymujesz te ostrzeżenia. Jeśli wiesz, co robisz (i wiesz, że naprawdę nigdy nie musisz pozbywać się subskrypcji), możesz wyłączyć tę samą @SuppressLint("CheckResult")metodę.
Victor Rendina,