Hasil berlangganan tidak digunakan

133

Saya telah meningkatkan ke Android Studio 3.1 hari ini, yang tampaknya telah menambahkan beberapa pemeriksaan serat lagi. Salah satu pemeriksaan serat ini adalah untuk subscribe()panggilan RxJava2 sekali pakai yang tidak disimpan dalam variabel. Misalnya, mendapatkan daftar semua pemain dari basis data Kamar saya:

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

Menghasilkan blok kuning besar dan tooltip ini:

Hasil subscribetidak digunakan

Cuplikan layar Android Studio.  Kode disorot dalam warna Kuning dengan tooltip.  Teks tooltip: Hasil berlangganan tidak digunakan.

Apa praktik terbaik untuk panggilan Rx sekali pakai seperti ini? Haruskah saya tetap memegang Disposabledan dispose()menyelesaikannya? Atau haruskah saya @SuppressLintterus bergerak?

Ini sepertinya hanya mempengaruhi RxJava2 ( io.reactivex), RxJava ( rx) tidak memiliki serat ini.

Michael Dodd
sumber
Dari kedua solusi Anda, saya benar-benar berpikir @SuppressLint bukan yang terbaik. Mungkin saya salah, tetapi saya benar-benar berpikir kode tidak boleh mengubah peringatan dan / atau petunjuk IDE
Arthur Attout
@ArthurAttout Setuju, saat ini saya memegang Disposableruang lingkup anggota dan menelepon dispose()ketika single selesai, tetapi tampaknya sia-sia rumit. Saya tertarik untuk melihat apakah ada cara yang lebih baik untuk melakukan ini.
Michael Dodd
8
Saya pikir peringatan serat ini mengganggu ketika aliran RxJava tidak berlangganan dari dalam dalam Activity / Fragment / ViewModel. Saya memiliki Completable yang dapat berjalan dengan aman tanpa memperhatikan siklus hidup Aktivitas, namun saya masih harus membuangnya?
EM
pertimbangkan RxLifecycle
최봉재

Jawaban:

122

IDE tidak tahu apa efek potensial yang dapat dimiliki langganan Anda ketika tidak dibuang, sehingga memperlakukannya sebagai berpotensi tidak aman. Misalnya, Anda Singlemungkin berisi panggilan jaringan, yang dapat menyebabkan kebocoran memori jika Anda Activityditinggalkan selama eksekusi.

Cara mudah untuk mengelola sejumlah besar Disposables adalah dengan menggunakan CompositeDisposable ; cukup buat CompositeDisposablevariabel instance baru di kelas terlampir Anda, lalu tambahkan semua Disposables Anda ke CompositeDisposable (dengan RxKotlin Anda bisa menambahkan addTo(compositeDisposable)semua Disposables Anda). Akhirnya, setelah selesai dengan instance Anda, teleponlah compositeDisposable.dispose().

Ini akan menghilangkan peringatan serat, dan memastikan Anda Disposablesdikelola dengan benar.

Dalam hal ini, kode tersebut akan terlihat seperti:

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
sumber
Saya mendapatkan kesalahan kompilasi error: cannot find symbol method addTo(CompositeDisposable)dengan "rxjava: 2.1.13". Dari mana metode ini berasal? (RxSwift atau RxKotlin saya kira)
aeracode
2
Ya, ini metode RxKotlin.
urgentx
1
apa yang harus dilakukan jika flowable
Hunt
Bagaimana jika kita melakukan ini di doOnSubscribe
Killer
2
Itu tidak akan menyebabkan kebocoran memori. Setelah panggilan jaringan selesai dan onComplete dipanggil, pengumpulan sampah akan menangani sisanya kecuali Anda menyimpan referensi yang jelas tentang sekali pakai dan jangan membuangnya.
Gabriel Vasconcelos
26

Saat Kegiatan akan dihancurkan, daftar Pakai akan dihapus dan kami baik-baik saja.

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

Anda dapat berlangganan dengan 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();
            }});

Jika Anda perlu langsung membuang Singleobjek (misalnya sebelum memancarkan), Anda dapat menerapkan metode onSubscribe(Disposable d)untuk mendapatkan dan menggunakan Disposablereferensi.

Anda juga dapat mewujudkan SingleObserverantarmuka dengan Anda sendiri atau menggunakan kelas anak lainnya.

papandreus
sumber
5

Seperti yang disarankan Anda dapat menggunakan global CompositeDisposableuntuk menambahkan hasil operasi berlangganan di sana.

The RxJava2Extensions perpustakaan berisi metode yang berguna untuk secara otomatis hapus dibuat pakai dari CompositeDisposablesaat itu selesai. Lihat bagian berlanggananAutoDispose .

Dalam kasus Anda mungkin terlihat seperti ini

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

Anda dapat menggunakan Uber AutoDispose dan rxjava.as

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

Pastikan Anda mengerti ketika Anda berhenti berlangganan berdasarkan ScopeProvider.

blaffie
sumber
Ini mengasumsikan penyedia siklus hidup tersedia. Juga, metode "as" ditandai sebagai tidak stabil, jadi menggunakannya akan menghasilkan peringatan Lint.
Dabbler
1
Terima kasih @Dabbler, setuju. The .as Metode adalah eksperimental sampai RxJava 2.1.7 dan 2.2 itu stabil.
blaffie
1

Berkali-kali saya menemukan diri saya kembali ke pertanyaan tentang cara membuang langganan dengan benar, dan khususnya pada posting ini. Beberapa blog dan ceramah mengklaim bahwa gagal menelepon disposetentu menyebabkan kebocoran memori, yang menurut saya terlalu umum. Dalam pemahaman saya, peringatan serat tentang tidak menyimpan hasil subscribeadalah tidak menjadi masalah dalam beberapa kasus, karena:

  • Tidak semua yang dapat diamati dijalankan dalam konteks aktivitas Android
  • Yang diamati bisa sinkron
  • Buang disebut secara implisit, asalkan selesai diamati

Karena saya tidak ingin menekan peringatan serat, saya baru-baru ini mulai menggunakan pola berikut untuk kasus dengan sinkron diamati:

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

Saya akan tertarik pada komentar tentang ini, terlepas dari apakah itu konfirmasi kebenaran atau ditemukannya celah.

Amatir
sumber
0

Ada cara lain yang tersedia, yaitu menghindari menggunakan Disposables secara manual (menambah dan menghapus langganan).

Anda dapat mendefinisikan Observable dan yang dapat diamati akan menerima konten dari SubjectBehaviour (jika Anda menggunakan RxJava). Dan dengan meneruskan itu ke LiveData Anda , itu akan berhasil. Lihatlah contoh berikut berdasarkan pertanyaan awal:

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

Jika Anda yakin sekali pakai ditangani dengan benar, misalnya menggunakan operator doOnSubscribe (), Anda dapat menambahkan ini ke Gradle:

android {
lintOptions {
     disable 'CheckResult'
}}
Ivan
sumber
10
Ini akan menekan pemeriksaan serat ini untuk semua contoh hasil yang tidak dicentang. Ada banyak kali di luar contoh OP di mana seseorang harus menangani hasil yang dikembalikan. Ini menggunakan palu godam untuk membunuh lalat.
tir38
16
Tolong jangan lakukan ini! Ada alasan mengapa Anda mendapat peringatan ini. Jika Anda tahu apa yang Anda lakukan (dan tahu Anda benar-benar tidak perlu membuang langganan Anda), Anda dapat menekan @SuppressLint("CheckResult")hanya pada metode.
Victor Rendina