Modo ideale per impostare un gestore di eccezioni non rilevate globale in Android


88

Voglio impostare un gestore di eccezioni non rilevato globale per tutti i thread nella mia applicazione Android. Quindi, nella mia Applicationsottoclasse ho impostato un'implementazione di Thread.UncaughtExceptionHandlercome gestore predefinito per eccezioni non rilevate.

Thread.setDefaultUncaughtExceptionHandler(
                new DefaultExceptionHandler(this));

Nella mia implementazione, sto cercando di visualizzare un AlertDialogmessaggio di eccezione appropriato.

Tuttavia, questo non sembra funzionare. Ogni volta che viene lanciata un'eccezione per un thread che non viene gestito, ottengo la finestra di dialogo predefinita del sistema operativo ("Sorry! -Application-has-stop-in modo imprevisto").

Qual è il modo corretto e ideale per impostare un gestore predefinito per eccezioni non rilevate?


1
Puoi condividere il codice per lo stesso ...
Code_Life

2
Se si desidera registrare le eccezioni, dare un'occhiata a acra.ch . ACRA ti consente di inviare rapporti di errore a un documento Google o a te tramite posta elettronica.
Alexander Pacha

1
@Alexander Oppure puoi semplicemente usare Google Analytics per Android e registrare tutte le eccezioni che desideri ...
IgorGanapolsky

Risposte:


24

Dovrebbe essere tutto ciò che devi fare. (Assicurati di interrompere il processo in seguito: le cose potrebbero essere in uno stato incerto.)

La prima cosa da verificare è se il gestore Android viene ancora chiamato. È possibile che la tua versione venga chiamata ma fallisca in modo irreversibile e system_server mostri una finestra di dialogo generica quando vede il crash del processo.

Aggiungi alcuni messaggi di registro nella parte superiore del gestore per vedere se ci sta arrivando. Stampa il risultato da getDefaultUncaughtExceptionHandler e quindi genera un'eccezione non rilevata per causare un arresto anomalo. Tieni d'occhio l'output di logcat per vedere cosa sta succedendo.


10
"lanciare un'eccezione non rilevata per causare un arresto anomalo dopo aver gestito l'errore" è ancora importante. L'ho appena sperimentato. La mia app è stata bloccata dopo aver gestito l'eccezione e non ha generato un'eccezione non rilevata.
OneWorld

@OneWorld Jepp lo stesso qui - almeno per la parte di blocco - sembra che non ci sia modo di "salvare" l'app dal blocco dopo tutto.
AgentKnopf

@Zainodis Ho postato una risposta leggermente fuori tema a questa domanda fornendo un collegamento a Crittercism - penso che abbiano una funzione che ti consente di "salvare" l'app dal blocco dopo tutto. Non sono sicuro: sto usando solo la versione gratuita atm.
Richard Le Mesurier

@RichardLeMesurier Grazie per il suggerimento - controllerò :)!
AgentKnopf

Strano!!! uncaughtexception chiamato anche se gestisco l'eccezione nella mia attività. Qualche idea.
Hiren Dabhi

12

Ho pubblicato la semplice soluzione per la gestione personalizzata degli arresti anomali di Android molto tempo fa. È un po 'hacky tuttavia funziona su tutte le versioni di Android (incluso il Lollipop).

Prima un po 'di teoria. I problemi principali quando si utilizza un gestore di eccezioni non rilevato in Android vengono con le eccezioni generate nel thread principale (noto anche come interfaccia utente). Ed ecco perché. Quando l'app si avvia, il sistema chiama il metodo ActivityThread.main che prepara e avvia il looper principale della tua app:

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

Il looper principale è responsabile dell'elaborazione dei messaggi pubblicati nel thread dell'interfaccia utente (inclusi tutti i messaggi relativi al rendering e all'interazione dell'interfaccia utente). Se viene generata un'eccezione nel thread dell'interfaccia utente, verrà rilevata dal gestore delle eccezioni, ma poiché non sei nel loop()metodo non sarai in grado di mostrare alcuna finestra di dialogo o attività all'utente poiché non è rimasto nessuno per elaborare i messaggi dell'interfaccia utente per te.

La soluzione proposta è abbastanza semplice. Eseguiamo il Looper.loopnostro metodo e lo circondiamo con il blocco try-catch. Quando viene rilevata un'eccezione, la elaboriamo come desideriamo (ad esempio, avvia la nostra attività di report personalizzato) e richiamiamo Looper.loopnuovamente il metodo.

Il seguente metodo dimostra questa tecnica (dovrebbe essere chiamata Application.onCreatedall'ascoltatore):

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);
        }
    }
}

Come puoi vedere, il gestore delle eccezioni non rilevate viene utilizzato solo per le eccezioni lanciate nei thread in background. Il seguente gestore rileva tali eccezioni e le propaga al thread dell'interfaccia utente:

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);
            }
        });
    }
}

Un progetto di esempio che utilizza questa tecnica è disponibile sul mio repository GitHub: https://github.com/idolon-github/android-crash-catcher


Come si potrebbe intuire, in questo modo non solo è possibile mostrare una finestra di dialogo di errore personalizzata, ma anche consentire all'utente di ignorare l'eccezione e continuare a lavorare con l'applicazione (e anche se sembra una cattiva idea per le app pubblicate, potrebbe essere abbastanza comodo durante una sessione di debug o di test).
Idolon

Ciao, anche se questo funziona per catturare eccezioni non rilevate ma, per qualche motivo, questo codice genera sempre eccezioni. Inizialmente pensavo che fosse a causa della riga RuntimeException che hai nel metodo startCatcher, ma ricevo ancora un'eccezione dopo averla rimossa. Non sono sicuro che sia una cosa richiesta. Comunque l'eccezione che ottengo è java.lang.RuntimeException: Performing pause of activity that is not resumed. Credo che quando provo ad avviare il mio looper il sistema metta in pausa l'attività che sta per iniziare? Non ne sono sicuro, ma qualsiasi aiuto sarebbe apprezzato. Grazie
sttaq

anche se rimuovo lo startCatcher cioè eseguo l'app senza il tuo codice allora tutto funziona bene senza eccezioni.
sttaq

@sttaq Quale versione di Android usi?
Idolon

Penso che stavo provando questo su 2.3.x
sttaq

3

Penso che per disabilitarlo nel tuo metodo uncaughtException () non chiamare previousHandler.uncaughtException () dove previousHandler è impostato da

previousHandler = Thread.getDefaultUncaughtExceptionHandler();

2

FWIW So che questo è leggermente fuori tema, ma abbiamo utilizzato con successo il piano gratuito di Crittercism . Offrono anche alcune funzionalità premium, come la gestione dell'eccezione in modo che l'app non si blocchi.

Nella versione gratuita, l'utente vede ancora il crash, ma almeno ricevo l'email e la traccia dello stack.

Usiamo anche la versione iOS (ma ho sentito dai miei colleghi che non è altrettanto buona).


Ecco domande simili:


1

Non funziona finché non chiami

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

alla fine del tuo UncaughtExceptionHandler.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.