Gestionnaire d'erreurs standard dans RxJava2 ou pourquoi RxJava provoque des plantages d'application même si onError est implémenté

L'article abordera UndeliverableException dans RxJava2 version 2.0.6 et 2.0.6 ultérieures. Si quelqu'un est entré en collision et ne peut pas le comprendre, ou n'a pas entendu parler de ce problème du tout - je demande un chat. Ils ont provoqué la traduction de problèmes de production après la transition de RxJava1 à RxJava2 . L'original a été écrit le 28 décembre 2017, mais il vaut mieux le découvrir tard que jamais.
Nous sommes tous de bons développeurs et nous RxJava erreurs dans onError lorsque nous utilisons RxJava . Cela signifie que nous nous sommes protégés contre les plantages d'applications, non?

Non, pas vrai.

Ci-dessous, nous examinerons quelques exemples dans lesquels l'application se RxJava raison de RxJava , même si onError correctement implémenté.

RxJava erreurs de base dans RxJava


RxJava utilise RxJavaPlugins.onError comme gestionnaire d'erreur de base. Il gère toutes les erreurs qui ne peuvent pas être transmises à l'abonné. Par défaut, toutes les erreurs lui sont envoyées, ce qui peut entraîner des plantages critiques des applications.
2.0.6 décrivent ce comportement:
L'un des objectifs de la conception 2.x est l'absence d'erreurs perdues. Parfois, une séquence se termine ou est annulée avant que la source ne déclenche onError . Dans ce cas, l'erreur n'a nulle part où aller et elle est envoyée à RxJavaPlugins.onError

Si RxJava n'a pas de gestionnaire d'erreurs de base, ces erreurs nous seront cachées et les développeurs seront dans l'ignorance des problèmes potentiels dans le code.

À partir de la version 2.0.6 , RxJavaPlugins.onError essaie d'être plus intelligent et partage les erreurs de bibliothèque / d'implémentation et les situations où une erreur ne peut pas être transmise. Les erreurs affectées à la catégorie des «bogues» sont appelées telles quelles, tandis que les autres sont encapsulées dans UndeliverableException puis appelées. Vous pouvez voir toute cette logique ici ( isBug onError et isBug ).

L'une des principales erreurs des débutants dans la RxJava OnErrorNotImplementedException est OnErrorNotImplementedException . Cette erreur se produit si observable provoque une erreur et que la méthode onError n'est pas implémentée dans l'abonné. Cette erreur est un exemple d'erreur qui pour le gestionnaire d'erreurs de base RxJava est un «bogue» et ne se transforme pas en une UndeliverableException .

UndeliverableException


Comme les erreurs liées aux «bugs» sont faciles à corriger, nous ne nous attarderons pas dessus. Les erreurs que RxJava dans une UndeliverableException sont plus intéressantes, car il n'est pas toujours évident de savoir pourquoi l'erreur ne peut pas être transmise à onError .

Les cas dans lesquels cela peut se produire dépendent de ce que les sources et les abonnés font spécifiquement. Nous considérerons les exemples ci-dessous, mais en général, nous pouvons dire qu'une telle erreur se produit s'il n'y a aucun abonné actif à qui l'erreur peut être transmise.

Exemple avec zipWith ()


La première option dans laquelle vous pouvez zipWith une UndeliverableException est l' 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() } ) 

Nous combinons deux sources ensemble, chacune provoquant une erreur. Qu'attendons-nous? Nous pouvons supposer que onError sera appelé deux fois, mais cela contredit la spécification des Reactive streams .
Après un seul appel à un événement terminal ( onError , onCompelete ), il est nécessaire de ne plus effectuer d'appels

Il s'avère qu'avec un seul appel à onError deuxième appel n'est plus possible. Que se passe-t-il lorsqu'une deuxième erreur se produit dans la source? Il sera livré à RxJavaPlugins.onError .

Un moyen simple de se retrouver dans cette situation consiste à utiliser zip pour combiner les appels réseau (par exemple, deux appels Retrofit renvoyant Observable ). Si une erreur se produit dans les deux appels (par exemple, il n'y a pas de connexion Internet), les deux sources provoqueront des erreurs, dont la première tombera dans l'implémentation onError et la seconde sera remise au gestionnaire d'erreurs de base ( RxJavaPlugins.onError ).

Exemple ConnectableObservable sans abonnés


ConnectableObservable peut également UndeliverableException une UndeliverableException . Il convient de rappeler que ConnectableObservable déclenche des événements indépendamment de la présence d'abonnés actifs, il suffit d'appeler la méthode connect() . Si une erreur se produit dans ConnectableObservable alors qu'il n'y a pas d'abonnés, elle sera transmise au gestionnaire d'erreurs de base.

Voici un exemple assez innocent qui pourrait provoquer une telle erreur:

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

Si someApi.retrofitCall() provoque une erreur (par exemple, il n'y a pas de connexion Internet), l'application se bloque car l'erreur réseau sera transmise au RxJava erreurs de base RxJava .

Cet exemple semble fictif, mais il est très facile de se retrouver dans une situation où ConnectableObservable est toujours connecté, mais il n'a pas d'abonnés. Je suis tombé sur cela lors de l'utilisation de autoConnect() lors de l'appel d'une API. autoConnect() ne désactive pas automatiquement Observable . Je me suis désabonné de la méthode d' Activity onStop , mais le résultat de l'appel réseau est revenu après la destruction de l' Activity et l'application UndeliverableException avec UndeliverableException .

Gestion des erreurs


Que faire de ces erreurs?

La première étape consiste à examiner les erreurs qui se produisent et à essayer de déterminer leurs causes. Idéalement, si vous pouvez résoudre le problème à sa source pour éviter que l'erreur ne soit RxJavaPlugins.onError à RxJavaPlugins.onError .

La solution pour l'exemple zipWith est de prendre une ou les deux sources et d'implémenter l' une des méthodes pour y détecter les erreurs. Par exemple, vous pouvez utiliser onErrorReturn pour transmettre la valeur par défaut au lieu d'une erreur.

L'exemple ConnectableObservable est plus simple à corriger - assurez-vous simplement de déconnecter Observable lorsque le dernier abonné se désabonne. autoConnect() , par exemple, a une implémentation surchargée qui prend une fonction qui attrape le temps de connexion ( plus peut être vu ici ).

Une autre façon de résoudre le problème consiste à remplacer le gestionnaire d'erreurs de base par le vôtre. La RxJavaPlugins.setErrorHandler(Consumer<Throwable>) vous y aidera. Si c'est la solution qui vous convient, vous pouvez intercepter toutes les erreurs envoyées à RxJavaPlugins.onError et les traiter comme bon vous semble. Cette solution peut être assez compliquée - n'oubliez pas que RxJavaPlugins.onError reçoit des erreurs de tous les flux RxJava de l'application.

Si vous créez manuellement votre Observable , vous pouvez appeler emitter.tryOnError() au lieu de emitter.tryOnError() . Cette méthode signale une erreur uniquement si le flux n'est pas terminé et a des abonnés. N'oubliez pas que cette méthode est expérimentale.

La morale de cet article est que vous ne pouvez pas être sûr qu'il n'y a aucune erreur lorsque vous travaillez avec RxJava si vous avez simplement implémenté onError dans les abonnés. Vous devez être conscient des situations dans lesquelles les erreurs peuvent ne pas être disponibles pour les abonnés et assurez-vous que ces situations sont gérées.

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


All Articles