Manipulador de erro padrão no RxJava2 ou por que o RxJava causa falhas no aplicativo, mesmo que o onError seja implementado

O artigo discutirá UndeliverableException no RxJava2 versão 2.0.6 e posterior. Se alguém colidiu e não conseguiu descobrir ou não ouviu nada sobre esse problema, peço um gato. Eles solicitaram a tradução de problemas na production após a transição de RxJava1 para RxJava2 . O original foi escrito em 28 de dezembro de 2017, mas é melhor descobrir tarde do que nunca.
Todos somos bons desenvolvedores e detectamos erros no onError quando usamos o RxJava . Isso significa que nos protegemos de falhas de aplicativos, certo?

Não, não é verdade.

Abaixo, veremos alguns exemplos nos quais o aplicativo RxJava devido ao RxJava , mesmo que o onError implementado corretamente.

Manipulador de erro básico no RxJava


RxJava usa RxJavaPlugins.onError como o manipulador de erros base. Ele lida com todos os erros que não podem ser entregues ao assinante. Por padrão, todos os erros são enviados a ele, para que falhas críticas do aplicativo possam ocorrer.
2.0.6 descrevem este comportamento:
Um dos objetivos do design 2.x é a falta de erros perdidos. Às vezes, uma sequência termina ou é cancelada antes que a fonte aumente onError . Nesse caso, o erro não tem para onde ir e é enviado para RxJavaPlugins.onError

Se o RxJava não tiver um manipulador de erros básico, esses erros serão ocultados para nós e os desenvolvedores ficarão no escuro sobre possíveis problemas no código.

A partir da versão 2.0.6 , RxJavaPlugins.onError tenta ser mais inteligente e compartilha erros de biblioteca / implementação e situações em que um erro não pode ser entregue. Os erros atribuídos à categoria de "bugs" são chamados como são, enquanto os demais são agrupados em UndeliverableException e depois chamados. Você pode ver toda essa lógica aqui ( isBug onError e isBug ).

Um dos principais erros dos novatos no RxJava OnErrorNotImplementedException é o OnErrorNotImplementedException . Este erro ocorre se observable causar um erro e o método onError não estiver implementado no assinante. Este erro é um exemplo de erro que, para o manipulador de erros básico, RxJava é um "bug" e não se transforma em uma UndeliverableException .

UndeliverableException


Como os erros relacionados a "bugs" são fáceis de corrigir, não iremos nos deter neles. Os erros que o RxJava envolve em uma UndeliverableException são mais interessantes, pois nem sempre é óbvio o motivo pelo qual o erro não pode ser entregue ao onError .

Os casos em que isso pode acontecer dependem do que as fontes e assinantes fazem especificamente. Consideraremos exemplos abaixo, mas, em geral, podemos dizer que esse erro ocorre se não houver assinante ativo para quem o erro possa ser entregue.

Exemplo com zipWith ()


A primeira opção na qual você pode gerar uma UndeliverableException é a 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 duas fontes juntas, cada uma delas causando um erro. O que esperamos? Podemos supor que onError será chamado duas vezes, mas isso contradiz a especificação de Reactive streams .
Após uma única chamada para um evento do terminal ( onError , onCompelete ), é necessário que não sejam feitas mais chamadas

Acontece que, com uma única chamada para onError segunda chamada não é mais possível. O que acontece quando ocorre um segundo erro na fonte? Ele será entregue ao RxJavaPlugins.onError .

Uma maneira fácil de entrar nessa situação é usar o zip para combinar chamadas de rede (por exemplo, duas chamadas de Retrofit retornando Observable ). Se ocorrer um erro nas duas chamadas (por exemplo, não há conexão com a Internet), ambas as fontes causarão erros, a primeira delas cairá na implementação onError e a segunda será entregue ao manipulador de erros base ( RxJavaPlugins.onError ).

Exemplo de ConnectableObservable sem assinantes


ConnectableObservable também pode gerar uma UndeliverableException . Vale lembrar que o ConnectableObservable gera eventos independentemente da presença de assinantes ativos, basta chamar o método connect() . Se ocorrer um erro no ConnectableObservable quando não houver assinantes, ele será entregue ao manipulador de erros base.

Aqui está um exemplo bastante inocente que pode causar esse erro:

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

Se someApi.retrofitCall() causar um erro (por exemplo, não há conexão com a Internet), o aplicativo RxJava , pois o erro de rede será entregue ao RxJava erros base do RxJava .

Este exemplo parece fictício, mas é muito fácil entrar em uma situação em que o ConnectableObservable ainda está conectado, mas não possui assinantes. Me deparei com isso ao usar o autoConnect() ao chamar uma API. autoConnect() não desativa automaticamente o Observable . onStop a inscrição no método onStop da Activity , mas o resultado da chamada de rede retornou após a destruição da Activity e o aplicativo UndeliverableException com UndeliverableException .

Tratamento de erros


Então, o que fazer com esses erros?

O primeiro passo é examinar os erros que ocorrem e tentar determinar o que os causa. Idealmente, se você puder corrigir o problema em sua origem para impedir que o erro seja RxJavaPlugins.onError ao RxJavaPlugins.onError .

A solução para o exemplo zipWith é pegar uma ou ambas as fontes e implementar um dos métodos para detectar erros nelas. Por exemplo, você pode usar onErrorReturn para passar o valor padrão em vez de um erro.

O exemplo do ConnectableObservable é mais simples de corrigir - basta desconectar o Observable quando o último assinante cancelar a inscrição. autoConnect() , por exemplo, possui uma implementação sobrecarregada que autoConnect() uma função que captura o tempo de conexão ( mais pode ser visto aqui ).

Outra maneira de resolver o problema é substituir o manipulador de erros base pelo seu. O RxJavaPlugins.setErrorHandler(Consumer<Throwable>) o ajudará com isso. Se esta é a solução certa para você, você pode capturar todos os erros enviados para RxJavaPlugins.onError e manipulá-los como achar melhor. Essa solução pode ser bastante complicada - lembre-se de que o RxJavaPlugins.onError recebe erros de todos os fluxos do RxJava no aplicativo.

Se você criar manualmente seu Observable , poderá chamar emitter.tryOnError() vez de emitter.tryOnError() . Este método relata um erro apenas se o fluxo não for encerrado e tiver assinantes. Lembre-se de que este método é experimental.

A moral deste artigo é que você não pode ter certeza de que não há erros ao trabalhar com o RxJava se você simplesmente implementou o onError nos assinantes. Você deve estar ciente das situações em que os erros podem não estar disponíveis para os assinantes e garantir que essas situações sejam tratadas.

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


All Articles