Handler Kesalahan Standar di RxJava2 atau mengapa RxJava menyebabkan crash aplikasi bahkan jika onError diimplementasikan

Artikel ini akan membahas RxJava2 di RxJava2 versi 2.0.6 dan yang lebih baru. Jika seseorang bertabrakan dan tidak bisa mengetahuinya, atau sama sekali tidak mendengar masalah ini - saya minta kucing. Mereka mendorong terjemahan masalah dalam production setelah transisi dari RxJava1 ke RxJava2 . Dokumen asli ditulis pada 28 Desember 2017, tetapi lebih baik untuk mencari tahu terlambat daripada tidak sama sekali.
Kita semua adalah pengembang yang baik dan menangkap kesalahan di onError ketika kita menggunakan RxJava . Ini berarti bahwa kami telah melindungi diri dari gangguan aplikasi, bukan?

Tidak, tidak benar.

Di bawah ini kita akan melihat beberapa contoh di mana aplikasi akan RxJava karena RxJava , bahkan jika onError diimplementasikan dengan benar.

Basic Error Handler di RxJava


RxJava menggunakan RxJavaPlugins.onError sebagai pengendali kesalahan dasar. Ini menangani semua kesalahan yang tidak bisa dikirim ke pelanggan. Secara default, semua kesalahan dikirim ke sana, sehingga crash aplikasi kritis dapat terjadi.
2.0.6 menggambarkan perilaku ini:
Salah satu tujuan dari desain 2.x adalah kurangnya kesalahan yang hilang. Terkadang urutan berakhir atau dibatalkan sebelum sumber meningkatkan onError . Dalam hal ini, kesalahan tidak memiliki tempat untuk pergi dan dikirim ke RxJavaPlugins.onError

Jika RxJava tidak memiliki penangan kesalahan dasar, kesalahan seperti itu akan disembunyikan dari kami dan pengembang akan berada dalam kegelapan tentang potensi masalah dalam kode.

Dimulai dengan versi 2.0.6 , RxJavaPlugins.onError mencoba menjadi lebih cerdas dan berbagi pustaka / kesalahan implementasi dan situasi saat kesalahan tidak dapat disampaikan. Kesalahan yang diberikan pada kategori "bug" disebut apa adanya, sedangkan sisanya dibungkus dalam UndeliverableException dan kemudian dipanggil. Anda dapat melihat semua logika ini di sini ( isBug dan isBug ).

Salah satu kesalahan utama pemula dalam RxJava OnErrorNotImplementedException adalah OnErrorNotImplementedException . Kesalahan ini terjadi jika observable menyebabkan kesalahan dan metode onError tidak diterapkan pada pelanggan. Kesalahan ini adalah contoh kesalahan yang untuk penangan kesalahan dasar RxJava adalah "bug" dan tidak berubah menjadi UndeliverableException .

Pengecualian tidak terkirim


Karena kesalahan yang terkait dengan "bug" mudah diperbaiki, kami tidak akan membahasnya. Kesalahan yang RxJava bungkus dalam RxJava lebih menarik, karena mungkin tidak selalu jelas mengapa kesalahan tidak dapat dikirim ke onError .

Kasus-kasus di mana hal ini dapat terjadi tergantung pada apa yang dilakukan sumber dan pelanggan secara khusus. Kami akan mempertimbangkan contoh-contoh di bawah ini, tetapi secara umum kami dapat mengatakan bahwa kesalahan semacam itu terjadi jika tidak ada pelanggan aktif kepada siapa kesalahan tersebut dapat disampaikan.

Contoh dengan zipWith ()


Opsi pertama di mana Anda dapat meningkatkan zipWith .

 val observable1 = Observable.error<Int>(Exception()) val observable2 = Observable.error<Int>(Exception()) val zipper = BiFunction<Int, Int, String> { one, two -> "$one - $two" } observable1.zipWith(observable2, zipper) .subscribe( { System.out.println(it) }, { it.printStackTrace() } ) 

Kami menggabungkan dua sumber bersama-sama, yang masing-masing menyebabkan kesalahan. Apa yang kita harapkan Kita dapat mengasumsikan bahwa onError akan dipanggil dua kali, tetapi ini bertentangan dengan spesifikasi Reactive streams .
Setelah satu panggilan ke acara terminal ( onError , onCompelete ), diperlukan bahwa tidak ada lagi panggilan yang dilakukan

Ternyata dengan satu panggilan ke onError panggilan kedua tidak lagi mungkin. Apa yang terjadi ketika kesalahan kedua terjadi di sumber? Ini akan dikirimkan ke RxJavaPlugins.onError .

Cara mudah untuk masuk ke situasi ini adalah dengan menggunakan zip untuk menggabungkan panggilan jaringan (misalnya, dua panggilan Retrofit Observable ). Jika kesalahan terjadi di kedua panggilan (misalnya, tidak ada koneksi Internet), kedua sumber akan menyebabkan kesalahan, yang pertama akan jatuh ke dalam implementasi onError , dan yang kedua akan dikirim ke penangan kesalahan dasar ( RxJavaPlugins.onError ).

Contoh ConnectableObservable tanpa pelanggan


ConnectableObservable juga dapat meningkatkan UndeliverableException . Patut diingat bahwa ConnectableObservable memunculkan peristiwa terlepas dari keberadaan pelanggan aktif, panggil saja metode connect() . Jika kesalahan terjadi di ConnectableObservable ketika tidak ada pelanggan, itu akan dikirimkan ke penangan kesalahan dasar.

Berikut adalah contoh yang sangat tidak bersalah yang dapat menyebabkan kesalahan seperti itu:

 someApi.retrofitCall() //     Retrofit .publish() .connect() 

Jika someApi.retrofitCall() menyebabkan kesalahan (misalnya, tidak ada koneksi Internet), aplikasi akan macet karena kesalahan jaringan akan dikirim ke RxJava kesalahan dasar RxJava .

Contoh ini tampaknya fiktif, tetapi sangat mudah untuk masuk ke situasi di mana ConnectableObservable masih terhubung, tetapi tidak memiliki pelanggan. Saya menemukan ini ketika menggunakan autoConnect() saat memanggil API. autoConnect() tidak secara otomatis menonaktifkan Observable . Saya berhenti berlangganan dalam metode Activity onStop , tetapi hasil panggilan jaringan kembali setelah penghancuran Activity dan aplikasi UndeliverableException dengan UndeliverableException .

Menangani kesalahan


Jadi apa yang harus dilakukan dengan kesalahan ini?

Langkah pertama adalah melihat kesalahan yang terjadi dan mencoba menentukan apa yang menyebabkannya. Idealnya, jika Anda dapat memperbaiki masalah pada sumbernya untuk mencegah kesalahan dari RxJavaPlugins.onError ke RxJavaPlugins.onError .

Solusi untuk contoh zipWith adalah dengan mengambil satu atau kedua sumber dan mengimplementasikan salah satu metode untuk menangkap kesalahan di dalamnya. Misalnya, Anda dapat menggunakan onErrorReturn untuk meneruskan nilai default alih-alih kesalahan.

Contoh ConnectableObservable lebih mudah untuk diperbaiki - hanya pastikan Anda memutuskan Observable ketika pelanggan terakhir berhenti berlangganan. autoConnect() , misalnya, memiliki implementasi kelebihan beban yang mengambil fungsi yang menangkap waktu koneksi ( lebih banyak dapat dilihat di sini ).

Cara lain untuk memecahkan masalah adalah mengganti handler kesalahan dasar dengan Anda sendiri. Metode RxJavaPlugins.setErrorHandler(Consumer<Throwable>) akan membantu Anda dengan ini. Jika ini adalah solusi yang tepat untuk Anda, Anda dapat menangkap semua kesalahan yang dikirim ke RxJavaPlugins.onError dan menanganinya sesuai keinginan Anda. Solusi ini bisa sangat rumit - ingat bahwa RxJavaPlugins.onError menerima kesalahan dari semua aliran RxJava dalam aplikasi.

Jika Anda secara manual membuat Observable Anda, Anda dapat memanggil emitter.tryOnError() alih-alih emitter.tryOnError() . Metode ini melaporkan kesalahan hanya jika aliran tidak dihentikan dan memiliki pelanggan. Ingat bahwa metode ini eksperimental.

Moral dari artikel ini adalah Anda tidak dapat memastikan bahwa tidak ada kesalahan ketika bekerja dengan RxJava jika Anda hanya menerapkan onError di pelanggan. Anda harus mengetahui situasi di mana kesalahan mungkin tidak tersedia untuk pelanggan, dan pastikan bahwa situasi ini ditangani.

Source: https://habr.com/ru/post/id422611/


All Articles