Maneira ideal de definir o manipulador de exceções globais não capturadas no Android

88

Quero definir um manipulador global de exceção não capturada para todos os threads em meu aplicativo Android. Portanto, em minha Applicationsubclasse, defini uma implementação de Thread.UncaughtExceptionHandlercomo manipulador padrão para exceções não detectadas.

Thread.setDefaultUncaughtExceptionHandler(
                new DefaultExceptionHandler(this));

Em minha implementação, estou tentando exibir uma AlertDialogmensagem de exceção apropriada exibindo.

No entanto, isso não parece funcionar. Sempre que, uma exceção é lançada para qualquer thread que não foi tratada, eu obtenho o estoque, caixa de diálogo padrão do SO ("Desculpe! Diálogo de aplicativo parou inesperadamente").

Qual é a maneira correta e ideal de definir um manipulador padrão para exceções não detectadas?

Samuh
fonte
1
Você pode compartilhar o código do mesmo ...
Code_Life
2
Se você deseja registrar suas exceções, ter um olhar para acra.ch . ACRA permite que você envie relatórios de erros para um Google-Doc ou para você via e-mail.
Alexander Pacha
1
@Alexander Ou você pode apenas usar o Google Analytics para Android e registrar todas as exceções que desejar ...
IgorGanapolsky
Isso pode ser de ajuda stackoverflow.com/questions/19897628/…
Aristo Michael

Respostas:

24

Isso deve ser tudo que você precisa fazer. (Certifique-se de fazer com que o processo pare depois - as coisas podem estar em um estado incerto.)

A primeira coisa a verificar é se o manipulador Android ainda está sendo chamado. É possível que sua versão esteja sendo chamada, mas falhando fatalmente e o system_server esteja mostrando uma caixa de diálogo genérica ao ver a falha do processo.

Adicione algumas mensagens de log no topo do seu manipulador para ver se ele está chegando lá. Imprima o resultado de getDefaultUncaughtExceptionHandler e, em seguida, lance uma exceção não capturada para causar um travamento. Fique de olho na saída do logcat para ver o que está acontecendo.

fadden
fonte
10
"lançar uma exceção não detectada para causar uma falha após você ter tratado o erro" ainda é importante. Eu apenas experimentei. Meu aplicativo foi bloqueado depois que lidei com a exceção e não lancei uma exceção não detectada.
OneWorld de
@OneWorld Jepp o mesmo aqui - pelo menos para a parte de bloqueio - parece não haver maneira de "salvar" o aplicativo de travar, afinal.
AgentKnopf
@Zainodis Eu postei uma resposta levemente fora do tópico para esta questão dando um link para Crittercism - eu acho que eles têm um recurso que permite que você "salve" o aplicativo de travar, afinal. Não tenho certeza - estou usando apenas a versão gratuita do atm.
Richard Le Mesurier
@RichardLeMesurier Obrigado pela dica - vou dar uma olhada :)!
AgentKnopf
Estranho!!! uncaughtexception chamado mesmo se eu tratar a exceção em minha atividade. Qualquer ideia.
Hiren Dabhi,
12

Publiquei a solução simples para tratamento personalizado de travamentos do Android há muito tempo. É um pouco hacky, no entanto, funciona em todas as versões do Android (incluindo o Lollipop).

Primeiro um pouco de teoria. Os principais problemas ao usar o manipulador de exceções não detectadas no Android vêm com as exceções lançadas no thread principal (também conhecido como UI). E aqui está o porquê. Quando o aplicativo é iniciado, o sistema chama o método ActivityThread.main , que prepara e inicia o looper principal do seu aplicativo:

public static void main(String[] args) {
  
  
    Looper.prepareMainLooper();
  
    Looper.loop();
    throw new RuntimeException("Main thread loop unexpectedly exited");
}

O looper principal é responsável por processar as mensagens postadas no thread da IU (incluindo todas as mensagens relacionadas à renderização e interação da IU). Se uma exceção for lançada no thread de IU, ela será capturada por seu manipulador de exceções, mas como você está fora do loop()método, não será capaz de mostrar nenhuma caixa de diálogo ou atividade ao usuário, pois não sobrou ninguém para processar mensagens de IU para voce.

A solução proposta é bastante simples. Executamos o Looper.loopmétodo por conta própria e o envolvemos com o bloco try-catch. Quando uma exceção é detectada, nós a processamos como queremos (por exemplo, iniciamos nossa atividade de relatório personalizado) e chamamos o Looper.loopmétodo novamente.

O método a seguir demonstra essa técnica (deve ser chamado a partir do Application.onCreateouvinte):

private void startCatcher() {
    UncaughtExceptionHandler systemUncaughtHandler = Thread.getDefaultUncaughtExceptionHandler();

    // the following handler is used to catch exceptions thrown in background threads
    Thread.setDefaultUncaughtExceptionHandler(new UncaughtHandler(new Handler()));

    while (true) {
        try {
            Looper.loop();
            Thread.setDefaultUncaughtExceptionHandler(systemUncaughtHandler);
            throw new RuntimeException("Main thread loop unexpectedly exited");
        } catch (Throwable e) {
            showCrashDisplayActivity(e);
        }
    }
}

Como você pode ver, o manipulador de exceções não capturadas é usado apenas para as exceções lançadas em threads de fundo. O manipulador a seguir captura essas exceções e as propaga para o thread de IU:

static class UncaughtHandler implements UncaughtExceptionHandler {

    private final Handler mHandler;

    UncaughtHandler(Handler handler) {
        mHandler = handler;
    }

    public void uncaughtException(Thread thread, final Throwable e) {
        mHandler.post(new Runnable() {
            public void run() {
                throw new BackgroundException(e);
            }
        });
    }
}

Um exemplo de projeto que usa essa técnica está disponível no meu repositório GitHub: https://github.com/idolon-github/android-crash-catcher

Idolon
fonte
Como se pode imaginar, desta forma não é apenas possível mostrar a caixa de diálogo de erro personalizada, mas também permitir que o usuário ignore a exceção e continue a trabalhar com o aplicativo (e embora pareça uma má ideia para os aplicativos publicados, pode ser bastante conveniente durante uma sessão de depuração ou teste).
Idolon
Oi, embora isso funcione na captura de exceções não detectadas, mas, por algum motivo, este código está sempre lançando exceções. Inicialmente, pensei que fosse por causa da linha RuntimeException que você tem no método startCatcher, mas ainda estou recebendo uma exceção após removê-la. Não tenho certeza se isso é uma coisa necessária. De qualquer forma, a exceção que recebo é java.lang.RuntimeException: Performing pause of activity that is not resumed. Acredito que quando estou tentando iniciar meu próprio looper o sistema pausa a atividade que está prestes a começar? Não tenho certeza, mas qualquer ajuda seria apreciada. Obrigado
sttaq
também se eu remover o startCatcher, ou seja, executar o aplicativo sem o seu código, tudo funcionará bem, sem exceções.
sttaq
@sttaq Qual versão do Android você usa?
Idolon
Acho que estava tentando fazer isso em 2.3.x
sttaq
3

Eu acho que para desativar isso em seu método uncaughtException () não chame previousHandler.uncaughtException () onde previousHandler é definido por

previousHandler = Thread.getDefaultUncaughtExceptionHandler();
Robby Pond
fonte
2

FWIW Eu sei que isso é um pouco fora do assunto, mas temos usado o plano gratuito do Crittercism com sucesso. Eles também oferecem alguns recursos premium, como lidar com a exceção para que o aplicativo não trave.

Na versão gratuita, o usuário ainda vê o travamento, mas pelo menos recebo o e-mail e o rastreamento da pilha.

Também usamos a versão iOS (mas ouvi de meus colegas que não é tão boa).


Aqui estão perguntas semelhantes:

Richard Le Mesurier
fonte
1

Não funciona até você ligar

android.os.Process.killProcess(android.os.Process.myPid());

no final do seu UncaughtExceptionHandler.

Joseph_Marzbani
fonte