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