Il risultato dell'iscrizione non viene utilizzato


133

Ho eseguito l'aggiornamento ad Android Studio 3.1 oggi, che sembra aver aggiunto alcuni controlli di lanugine. Uno di questi controlli di lanugine è per le subscribe()chiamate RxJava2 one-shot che non sono archiviate in una variabile. Ad esempio, ottenere un elenco di tutti i giocatori dal mio database Room:

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

Risultati in un grande blocco giallo e questa descrizione comandi:

Il risultato di subscribenon viene utilizzato

Schermata di Android Studio.  Il codice è evidenziato in giallo con una descrizione comandi.  Testo della descrizione comando: il risultato dell'iscrizione non viene utilizzato.

Qual è la migliore pratica per chiamate Rx one-shot come questa? Devo tenere premuto il Disposablee dispose()su completo? O dovrei solo @SuppressLintandare avanti?

Questo sembra influenzare solo RxJava2 ( io.reactivex), RxJava ( rx) non ha questa lanugine.


Di entrambe le soluzioni, onestamente penso che @SuppressLint non sia la migliore. Forse mi sbaglio, ma penso davvero che il codice non dovrebbe mai alterare gli avvertimenti e / o i suggerimenti
dell'IDE

@ArthurAttout D'accordo, attualmente sto tenendo in mano l' Disposableambito membro e chiamando dispose()quando il singolo viene completato, ma sembra inutilmente ingombrante. Sono interessato a vedere se ci sono modi migliori per farlo.
Michael Dodd,

8
Penso che questo avviso di lanugine sia fastidioso quando il flusso RxJava non è abbonato da all'interno di Activity / Fragment / ViewModel. Ho un Completable che può essere eseguito in sicurezza senza alcun riguardo per il ciclo di vita dell'Attività, ma devo ancora smaltirlo?
EM

considera RxLifecycle
최봉재

Risposte:


122

L'IDE non sa quali potenziali effetti può avere l'abbonamento quando non viene eliminato, quindi lo considera potenzialmente pericoloso. Ad esempio, Singlepotrebbe contenere una chiamata di rete, che potrebbe causare una perdita di memoria se Activityviene abbandonato durante la sua esecuzione.

Un modo conveniente per gestire una grande quantità di Disposables è usare un CompositeDisposable ; basta creare una nuova CompositeDisposablevariabile di istanza nella classe che lo racchiude, quindi aggiungere tutti i materiali monouso a CompositeDisposable (con RxKotlin è possibile semplicemente aggiungere addTo(compositeDisposable)tutti i materiali monouso). Alla fine, quando hai finito con la tua istanza, chiama compositeDisposable.dispose().

Questo eliminerà gli avvisi di lanugine e garantirà la Disposablescorretta gestione.

In questo caso, il codice sarebbe simile a:

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

Ricevo un errore di compilazione error: cannot find symbol method addTo(CompositeDisposable)con "rxjava: 2.1.13". Da dove viene questo metodo? (Suppongo che RxSwift o RxKotlin)
codice aereo

2
Sì, è un metodo RxKotlin.
urgentx,

1
cosa fare in caso di fluidità
caccia il

E se lo stiamo facendo in doOnSubscribe
Killer il

2
Non provocherebbe una perdita di memoria. Una volta terminata la chiamata di rete e chiamata onComplete, la garbage collection si occuperà del resto a meno che tu non abbia conservato un riferimento esplicito del monouso e non lo smaltisca.
Gabriel Vasconcelos,

26

Nel momento in cui l'attività verrà distrutta, l'elenco dei prodotti usa e getta viene cancellato e siamo a posto.

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

9

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

Nel caso in cui sia necessario disporre direttamente l' Singleoggetto (ad es. Prima dell'emissione), è possibile implementare il metodo onSubscribe(Disposable d)per ottenere e utilizzare il Disposableriferimento.

Puoi anche realizzare l' SingleObserverinterfaccia da solo o utilizzare altre classi secondarie.


5

Come è stato suggerito, è possibile utilizzare alcuni globali CompositeDisposableper aggiungere lì il risultato dell'operazione di iscrizione.

La libreria RxJava2Extensions contiene metodi utili per rimuovere automaticamente gli oggetti monouso creati dal CompositeDisposablecompletamento. Vedi la sezione iscriviti AutoDispose.

Nel tuo caso potrebbe apparire così

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

2

È possibile utilizzare Uber AutoDispose e rxjava.as

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

Assicurati di capire quando annulli l'iscrizione in base a ScopeProvider.


Ciò presuppone che sia disponibile un provider del ciclo di vita. Inoltre, il metodo "as" è contrassegnato come instabile, pertanto il suo utilizzo genererà un avviso Lint.
Dabbler

1
Grazie @Dabbler, d'accordo. Il metodo .as era sperimentale fino a RxJava 2.1.7 e su 2.2 è stabile.
Blaffie,

1

Ancora e ancora mi ritrovo a tornare alla domanda su come smaltire correttamente gli abbonamenti, e in particolare a questa pubblicazione. Diversi blog e conferenze sostengono che non riuscire a chiamare disposeporta necessariamente a una perdita di memoria, che penso sia un'affermazione troppo generale. A mio avviso, subscribein alcuni casi l'avvertimento relativo al non memorizzare il risultato è un problema, poiché:

  • Non tutti gli osservabili vengono eseguiti nel contesto di un'attività Android
  • L'osservabile può essere sincrono
  • Dispose viene chiamato implicitamente, a condizione che l'osservabile venga completato

Dato che non voglio sopprimere gli avvisi sui filacci, di recente ho iniziato a utilizzare il modello seguente per i casi con un osservatore sincrono:

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

Sarei interessato a qualsiasi commento su questo, indipendentemente dal fatto che si tratti di una conferma di correttezza o della scoperta di una scappatoia.


0

C'è un altro modo disponibile, che sta evitando di usare i prodotti usa e getta manualmente (aggiungere e rimuovere gli abbonamenti).

È possibile definire un osservabile e tale osservabile riceverà il contenuto da un soggetto (nel caso in cui si utilizzi RxJava). E passando quello osservabile al tuo LiveData , dovrebbe funzionare. Guarda il prossimo esempio basato sulla domanda iniziale:

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

Se si è certi che il monouso sia gestito correttamente, ad esempio utilizzando l'operatore doOnSubscribe (), è possibile aggiungere questo a Gradle:

android {
lintOptions {
     disable 'CheckResult'
}}

10
Questo eliminerà questo controllo lanugine per tutte le istanze di un risultato non controllato. Esistono molte volte al di fuori dell'esempio del PO in cui qualcuno dovrebbe gestire il risultato restituito. Questo sta usando una mazza per uccidere una mosca.
tir38

16
Per favore, non farlo! C'è un motivo per cui stai ricevendo questi avvisi. Se sai cosa stai facendo (e sai che davvero non hai mai bisogno di smaltire l'abbonamento) puoi sopprimerlo @SuppressLint("CheckResult")solo con il metodo.
Victor Rendina,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.