Controlador de errores estándar en RxJava2 o por qué RxJava provoca bloqueos de la aplicación incluso si se implementa onError

El artículo discutirá RxJava2 en RxJava2 versión 2.0.6 y posterior. Si alguien chocó y no puede resolverlo, o no escuchó sobre este problema en absoluto, le pido un gato. Impulsaron la traducción de problemas en la production después de la transición de RxJava1 a RxJava2 . El original fue escrito el 28 de diciembre de 2017, pero es mejor averiguarlo tarde que nunca.
Todos somos buenos desarrolladores y onError errores en onError cuando usamos RxJava . Esto significa que nos hemos protegido de los bloqueos de aplicaciones, ¿verdad?

No, no es verdad

A continuación, veremos un par de ejemplos en los que la aplicación se RxJava debido a RxJava , incluso si onError implementa correctamente.

RxJava errores básicos en RxJava


RxJava usa RxJavaPlugins.onError como el controlador de errores base. Maneja todos los errores que no se pueden entregar al suscriptor. Por defecto, todos los errores se envían, por lo que pueden producirse bloqueos críticos de la aplicación.
2.0.6 describen este comportamiento:
Uno de los objetivos del diseño 2.x es la falta de errores perdidos. A veces, una secuencia termina o se cancela antes de que la fuente onError . En este caso, el error no tiene a dónde ir y se envía a RxJavaPlugins.onError

Si RxJava no tiene un controlador de errores básico, dichos errores se ocultarán a nosotros y los desarrolladores no estarán al tanto de posibles problemas en el código.

A partir de la versión 2.0.6 , RxJavaPlugins.onError intenta ser más inteligente y comparte errores de biblioteca / implementación y situaciones en las que no se puede entregar un error. Los errores asignados a la categoría de "errores" se llaman como están, mientras que el resto se envuelven en UndeliverableException y luego se llaman. Puede ver toda esta lógica aquí ( isBug onError e isBug ).

Uno de los principales errores de los novatos en el RxJava OnErrorNotImplementedException es OnErrorNotImplementedException . Este error ocurre si observable causa un error y el método onError no está implementado en el suscriptor. Este error es un ejemplo de un error que para el controlador de errores básico RxJava es un "error" y no se convierte en una UndeliverableException no UndeliverableException .

Excepción imposible de entregar


Dado que los errores relacionados con los "errores" son fáciles de corregir, no nos detendremos en ellos. Los errores que RxJava envuelve en una RxJava son más interesantes, ya que no siempre es obvio por qué el error no se puede entregar a onError .

Los casos en que esto puede suceder dependen de lo que las fuentes y los suscriptores hagan específicamente. Consideraremos los ejemplos a continuación, pero en general podemos decir que tal error ocurre si no hay un suscriptor activo a quien se le pueda entregar el error.

Ejemplo con zipWith ()


La primera opción en la que puede generar una zipWith es la 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() } ) 

Combinamos dos fuentes juntas, cada una de las cuales causa un error. Que esperamos Podemos suponer que onError se llamará dos veces, pero esto contradice la especificación de Reactive streams .
Después de una sola llamada a un evento de terminal ( onError , onCompelete ), se requiere que no se realicen más llamadas

Resulta que con una sola llamada a onError segunda llamada ya no es posible. ¿Qué sucede cuando se produce un segundo error en la fuente? Se entregará a RxJavaPlugins.onError .

Una manera fácil de entrar en esta situación es usar zip para combinar llamadas de red (por ejemplo, dos llamadas de Retrofit devuelven Observable ). Si se produce un error en ambas llamadas (por ejemplo, no hay conexión a Internet), ambas fuentes causarán errores, el primero de los cuales caerá en la implementación onError y el segundo se entregará al controlador de errores base ( RxJavaPlugins.onError ).

Ejemplo de ConnectableObservable sin suscriptores


ConnectableObservable también puede generar una UndeliverableException . Vale la pena recordar que ConnectableObservable genera eventos independientemente de la presencia de suscriptores activos, solo llame al método connect() . Si se produce un error en ConnectableObservable cuando no hay suscriptores, se entregará al controlador de errores base.

Aquí hay un ejemplo bastante inocente que podría causar tal error:

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

Si someApi.retrofitCall() causa un error (por ejemplo, no hay conexión a Internet), la aplicación se bloqueará porque el error de red se entregará al RxJava errores base RxJava .

Este ejemplo parece ficticio, pero es muy fácil entrar en una situación en la que ConnectableObservable todavía está conectado, pero no tiene suscriptores. Me encontré con esto al usar autoConnect() al llamar a una API. autoConnect() no deshabilita automáticamente Observable . Me onStop en el método de Activity onStop , pero el resultado de la llamada de red regresó después de la destrucción de la Activity y la aplicación se UndeliverableException con UndeliverableException .

Manejo de errores


Entonces, ¿qué hacer con estos errores?

El primer paso es observar los errores que ocurren e intentar determinar qué los causa. Idealmente, si puede solucionar el problema en su origen para evitar que el error se RxJavaPlugins.onError a RxJavaPlugins.onError .

La solución para el ejemplo zipWith es tomar una o ambas fuentes e implementar uno de los métodos para detectar errores en ellas. Por ejemplo, puede usar onErrorReturn para pasar el valor predeterminado en lugar de un error.

El ejemplo de ConnectableObservable es más sencillo de solucionar, solo asegúrese de desconectar Observable cuando el último suscriptor se da de baja. autoConnect() , por ejemplo, tiene una implementación sobrecargada que toma una función que captura el tiempo de conexión ( se puede ver más aquí ).

Otra forma de resolver el problema es reemplazar el controlador de errores base por el suyo. El RxJavaPlugins.setErrorHandler(Consumer<Throwable>) lo ayudará con esto. Si esta es la solución adecuada para usted, puede detectar todos los errores enviados a RxJavaPlugins.onError y manejarlos como mejor le parezca. Esta solución puede ser bastante complicada: recuerde que RxJavaPlugins.onError recibe errores de todas RxJava transmisiones de RxJava en la aplicación.

Si crea manualmente su Observable , puede llamar a emitter.tryOnError() lugar de emitter.tryOnError() . Este método informa un error solo si la transmisión no finaliza y tiene suscriptores. Recuerda que este método es experimental.

La moraleja de este artículo es que no puede estar seguro de que no haya errores al trabajar con RxJava si simplemente implementó onError en los suscriptores. Debe tener en cuenta las situaciones en las que los errores pueden no estar disponibles para los suscriptores y asegurarse de que se manejan estas situaciones.

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


All Articles