Das Ergebnis des Abonnements wird nicht verwendet

133

Ich habe heute ein Upgrade auf Android Studio 3.1 durchgeführt, das anscheinend einige weitere Flusenprüfungen hinzugefügt hat. Eine dieser Flusenprüfungen betrifft einmalige RxJava2- subscribe()Aufrufe, die nicht in einer Variablen gespeichert sind. Zum Beispiel, um eine Liste aller Spieler aus meiner Raumdatenbank zu erhalten:

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

Das Ergebnis ist ein großer gelber Block und dieser Tooltip:

Das Ergebnis von subscribewird nicht verwendet

Screenshot von Android Studio.  Der Code wird mit einem Tooltip gelb hervorgehoben.  Tooltip-Text: Das Ergebnis des Abonnements wird nicht verwendet.

Was ist die beste Vorgehensweise für solche One-Shot-Rx-Anrufe? Soll ich das Disposableund dispose()auf vollständig halten? Oder soll ich einfach @SuppressLintweitermachen?

Dies scheint nur RxJava2 ( io.reactivex) zu betreffen , RxJava ( rx) hat diese Flusen nicht.

Michael Dodd
quelle
Von beiden Lösungen denke ich ehrlich, dass @SuppressLint nicht die beste ist. Vielleicht irre ich mich, aber ich denke wirklich, dass Code niemals die IDE-Warnungen und / oder Hinweise ändern sollte
Arthur Attout
@ArthurAttout Einverstanden, derzeit halte ich den Disposableat-Member-Bereich fest und rufe an, dispose()wenn die Single fertig ist, aber es scheint unnötig umständlich. Ich bin gespannt, ob es dafür bessere Möglichkeiten gibt.
Michael Dodd
8
Ich denke, diese Flusenwarnung ist ärgerlich, wenn der RxJava-Stream nicht aus einem Activity / Fragment / ViewModel heraus abonniert wurde. Ich habe ein Completable, das ohne Rücksicht auf den Aktivitätslebenszyklus sicher ausgeführt werden kann, aber ich muss es trotzdem entsorgen.
EM
Betrachten Sie RxLifecycle
최봉재

Antworten:

122

Die IDE weiß nicht, welche potenziellen Auswirkungen Ihr Abonnement haben kann, wenn es nicht entsorgt wird, und behandelt es daher als potenziell unsicher. Beispielsweise kann Ihr SingleAnruf einen Netzwerkanruf enthalten, der zu einem Speicherverlust führen kann, wenn Ihr Anruf Activitywährend der Ausführung abgebrochen wird.

Eine bequeme Möglichkeit, eine große Menge von Disposables zu verwalten, ist die Verwendung eines CompositeDisposable . Erstellen Sie einfach eine neue CompositeDisposableInstanzvariable in Ihrer umschließenden Klasse und fügen Sie dann alle Ihre Disposables zum CompositeDisposable hinzu (mit RxKotlin können Sie einfach addTo(compositeDisposable)alle Ihre Disposables anhängen ). Wenn Sie mit Ihrer Instanz fertig sind, rufen Sie schließlich auf compositeDisposable.dispose().

Dadurch werden die Flusenwarnungen entfernt und sichergestellt, dass Sie Disposablesordnungsgemäß verwaltet werden.

In diesem Fall würde der Code folgendermaßen aussehen:

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()).
dringlich
quelle
Ich error: cannot find symbol method addTo(CompositeDisposable)erhalte einen Kompilierungsfehler mit "rxjava: 2.1.13". Woher kommt diese Methode? (RxSwift oder RxKotlin, nehme ich an)
Aeracode
2
Ja, es ist eine RxKotlin-Methode.
Urgentx
1
was im Fall von fließfähig machen
Jagd
Was ist, wenn wir dies in doOnSubscribe tun?
Killer
2
Es würde keinen Speicherverlust verursachen. Sobald der Netzwerkaufruf beendet ist und onComplete aufgerufen wird, wird die Speicherbereinigung den Rest erledigen, es sei denn, Sie haben eine explizite Referenz des Einwegartikels aufbewahrt und entsorgen ihn nicht.
Gabriel Vasconcelos
26

In dem Moment, in dem die Aktivität zerstört wird, wird die Liste der Einwegartikel gelöscht und wir sind gut.

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
quelle
9

Sie können DisposableSingleObserver abonnieren :

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

Falls Sie ein SingleObjekt direkt entsorgen müssen (z. B. bevor es ausgegeben wird), können Sie eine Methode implementieren onSubscribe(Disposable d), um das Objekt abzurufen und zu verwendenDisposable Referenz .

Sie können die SingleObserverSchnittstelle auch selbst realisieren oder andere untergeordnete Klassen verwenden.

Papandreus
quelle
5

Wie vorgeschlagen, können Sie einige globale verwenden CompositeDisposable Ergebnis hinzufügen, um das Ergebnis des Abonnementvorgangs hinzuzufügen.

Die RxJava2Extensions- Bibliothek enthält nützliche Methoden zum automatischen Entfernen von erstellten Einwegartikeln nach CompositeDisposableAbschluss. Siehe subscribeAutoDispose Abschnitt .

In Ihrem Fall könnte es so aussehen

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

Sie können Uber AutoDispose und rxjava verwenden.as

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

Stellen Sie sicher, dass Sie verstehen, wann Sie sich mit dem ScopeProvider abmelden.

Blaffie
quelle
Dies setzt voraus, dass ein Lebenszyklusanbieter verfügbar ist. Außerdem wird die "as" -Methode als instabil markiert, sodass bei Verwendung eine Lint-Warnung angezeigt wird.
Dabbler
1
Danke @Dabbler, stimmte zu. Die .as- Methode war bis RxJava 2.1.7 experimentell und unter 2.2 stabil.
Blaffie
1

Immer wieder komme ich auf die Frage zurück, wie Abonnements korrekt entsorgt werden können, und insbesondere auf diesen Beitrag. In mehreren Blogs und Vorträgen wird behauptet, dass ein fehlgeschlagener Anruf disposezwangsläufig zu einem Speicherverlust führt, was meiner Meinung nach eine zu allgemeine Aussage ist. Nach meinem Verständnis ist die Flusenwarnung, dass das Ergebnis nicht gespeichert subscribewerden soll, in einigen Fällen kein Problem, da:

  • Nicht alle Observablen werden im Rahmen einer Android-Aktivität ausgeführt
  • Das Beobachtbare kann synchron sein
  • Dispose wird implizit aufgerufen, sofern das Observable abgeschlossen ist

Da ich Flusenwarnungen nicht unterdrücken möchte, habe ich kürzlich begonnen, das folgende Muster für Fälle mit einer synchronen beobachtbaren Größe zu verwenden:

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

Ich würde mich für Kommentare dazu interessieren, unabhängig davon, ob es sich um eine Bestätigung der Richtigkeit oder die Entdeckung einer Lücke handelt.

Dabbler
quelle
0

Es gibt eine andere Möglichkeit, die darin besteht, die manuelle Verwendung von Einwegartikeln zu vermeiden (Abonnements hinzufügen und entfernen).

Sie können ein Observable definieren und dieses Observable empfängt den Inhalt von einem SubjectBehaviour (falls Sie RxJava verwenden). Und indem Sie das Beobachtbare an Ihre LiveData weitergeben , sollte das funktionieren. Schauen Sie sich das nächste Beispiel anhand der ersten Frage an:

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
quelle
-10

Wenn Sie sicher sind, dass Einwegartikel korrekt behandelt werden, z. B. mit dem Operator doOnSubscribe (), können Sie dies zu Gradle hinzufügen:

android {
lintOptions {
     disable 'CheckResult'
}}
Ivan
quelle
10
Dadurch wird diese Flusenprüfung für alle Fälle eines ungeprüften Ergebnisses unterdrückt. Außerhalb des OP-Beispiels gibt es viele Fälle, in denen jemand mit dem zurückgegebenen Ergebnis umgehen sollte. Hierbei wird ein Vorschlaghammer verwendet, um eine Fliege zu töten.
Tir38
16
Bitte tu das nicht! Es gibt einen Grund, warum Sie diese Warnungen erhalten. Wenn Sie wissen, was Sie tun (und wissen, dass Sie Ihr Abonnement wirklich nie entsorgen müssen), können Sie @SuppressLint("CheckResult")nur die Methode unterdrücken .
Victor Rendina