Global Exception Handler — un meccanismo centralizzato per intercettare le eccezioni non gestite che impedisce l'arresto anomalo delle applicazioni mobili. Secondo Apple Developer, 2024, la corretta gestione delle eccezioni riduce il numero di crash del 40–60% e migliora l'esperienza utente. Senza un tale gestore, qualsiasi eccezione non gestita in un thread in background porta alla chiusura immediata dell'applicazione.
Punti Chiave
Global Exception Handler — è un meccanismo centralizzato per intercettare le eccezioni che non sono state gestite a livello di singole funzioni o moduli dell'applicazione. Nel contesto dello sviluppo mobile, tale gestore funge da ultima linea di difesa prima della terminazione anomala del processo.
iOS e Android forniscono API integrate per impostare un gestore globale. Apple utilizza NSSetUncaughtExceptionHandler per l'ambiente Objective-C, mentre Google offre Thread.setDefaultUncaughtExceptionHandler in Java/Kotlin. Entrambi i meccanismi intercettano le eccezioni che non sono state catturate dai costrutti try-catch su tutti i thread dell'applicazione.
Secondo Crashlytics (Google, 2024), circa il 25% dei crash si verifica a causa di eccezioni non gestite nei thread in background — un'area in cui il Global Exception Handler è particolarmente critico. Gli sviluppatori si concentrano spesso sul thread UI, dimenticando le operazioni asincrone.
Usare un gestore globale non sostituisce la gestione locale degli errori, ma la completa. Il compito principale è salvare il massimo delle informazioni sullo stato dell'applicazione al momento dell'eccezione e terminare correttamente.
Il meccanismo di funzionamento del Global Exception Handler si basa sull'intercettazione di segnali del sistema operativo o eccezioni in fase di esecuzione. Quando il codice lancia un'eccezione che non viene catturata da alcun blocco try-catch, il controllo viene trasferito a un gestore precedentemente registrato.
Su iOS, il gestore viene registrato tramite NSSetUncaughtExceptionHandler e riceve un oggetto NSException con uno stack trace completo. Su Android, si utilizza Thread.setDefaultUncaughtExceptionHandler, che accetta Thread e Throwable — fornendo accesso al tipo di eccezione, al messaggio e allo stack delle chiamate.
Dopo aver ricevuto i dati del crash, il gestore esegue tre azioni obbligatorie: scrivere un log nella memoria locale, inviare un report a Crashlytics o Sentry, e terminare correttamente l'applicazione. Secondo Apple WWDC 2023, il tempo di esecuzione del gestore è limitato a 5 secondi — dopo di che il sistema termina forzatamente il processo.
Per le applicazioni Swift a partire da iOS 13, è stata introdotta la Signals API, che gestisce non solo le eccezioni ma anche i segnali del sistema operativo — SIGABRT, SIGSEGV e SIGBUS, estendendo la copertura del gestore agli errori di memoria di basso livello.
L'implementazione di un gestore globale su iOS richiede l'impostazione di una funzione C tramite NSSetUncaughtExceptionHandler. Il gestore viene chiamato in modo sincrono al momento di un'eccezione non gestita e riceve il contesto completo dell'errore.
void handleUncaughtException(NSException exception) {
NSDictionary userInfo = [exception userInfo];
NSArray stackTrace = [exception callStackSymbols];
NSString reason = [exception reason];
// Salva il log del crash in un file locale
NSString logPath = [NSSearchPathForDirectoriesInDomains(
NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
[exceptionLog writeToFile:logPath atomically:YES];
}
int main(int argc, char argv[]) {
NSSetUncaughtExceptionHandler(&handleUncaughtException);
return UIApplicationMain(argc, argv, nil, nil);
}
Una caratteristica importante dell'implementazione iOS: il gestore cattura solo eccezioni Objective-C. Gli errori Swift che utilizzano il meccanismo throw-catch non raggiungono questo gestore — richiedono una gestione separata tramite Swift Error Handling. A partire da iOS 14, Apple raccomanda di combinare NSSetUncaughtExceptionHandler con la Signals API per la massima copertura.
Secondo Apple Technical Note TN2151, dopo la chiamata del gestore, l'applicazione deve terminare entro 5 secondi. Qualsiasi tentativo di continuare l'esecuzione dopo il ritorno dal gestore porta a un comportamento indefinito e a un successivo crash.
Android fornisce un meccanismo più flessibile per la gestione globale delle eccezioni tramite Thread.setDefaultUncaughtExceptionHandler. Il gestore riceve un riferimento al thread in cui si è verificata l'eccezione e l'oggetto Throwable stesso.
class GlobalExceptionHandler : Thread.UncaughtExceptionHandler {
override fun uncaughtException(thread: Thread, throwable: Throwable) {
// Salva il log del crash in un file
val stackTrace = throwable.stackTraceToString()
val crashLog = "CRASH: ${thread.name}\n${stackTrace}"
val file = File(context.cacheDir, "crash_log.txt")
file.writeText(crashLog)
// Invia a Crashlytics
FirebaseCrashlytics.getInstance()
.recordException(throwable)
// Termina il processo
android.os.Process.killProcess(
android.os.Process.myPid()
)
}
}
// Configurazione in Application.onCreate
class App : Application() {
override fun onCreate() {
super.onCreate()
Thread.setDefaultUncaughtExceptionHandler(
GlobalExceptionHandler()
)
}
}
Una differenza chiave dell'implementazione Android: ogni thread ha il proprio gestore e setDefaultUncaughtExceptionHandler imposta il gestore per tutti i thread che non hanno un gestore individuale assegnato. Ciò garantisce una copertura globale — dal thread UI agli AsyncTask in background e alle coroutine.
Su Android 12+, c'è una limitazione: dopo aver chiamato uncaughtException, l'applicazione deve terminare entro 100 millisecondi. Se il gestore esegue operazioni lunghe, il sistema potrebbe uccidere il processo prima che il log venga scritto. Si consiglia di utilizzare un servizio in background per l'invio dei report di crash.
La prima regola — non tentare di ripristinare il funzionamento dell'applicazione dopo un'eccezione non gestita. Lo stato dell'applicazione dopo un crash è indefinito e continuare l'esecuzione può portare al danneggiamento dei dati utente.
Limite di tempo — il principale vincolo tecnico del Global Exception Handler. Su iOS è di 5 secondi, su Android — 100 millisecondi. All'interno del gestore, dovrebbe essere salvato solo un insieme minimo di dati: tipo di eccezione, stack delle chiamate e lo stato di alcune variabili chiave.
L'invio di richieste di rete, la scrittura nel database e la serializzazione complessa dovrebbero essere rinviati a un meccanismo differito — ad esempio, salvare il log in un file e inviarlo al successivo avvio dell'applicazione.
Servizi di crash-reporting — Firebase Crashlytics, Sentry, Bugsnag — impostano il proprio gestore globale. Se uno sviluppatore imposta un gestore personalizzato aggiuntivo, deve passare il controllo al sistema di crash-reporting dopo le proprie azioni. Su Android si utilizza la composizione dei gestori: eseguire la propria logica, quindi chiamare il gestore precedente.
Per Firebase Crashlytics, si raccomanda di non impostare affatto un Thread.setDefaultUncaughtExceptionHandler personalizzato — l'SDK Crashlytics lo fa automaticamente all'inizializzazione.
Contesto utente — oltre allo stack delle chiamate standard, è utile registrare la versione dell'applicazione, la versione del sistema operativo, la dimensione della memoria disponibile e il tempo di attività prima del crash. Questi dati sono fondamentali per riprodurre e risolvere il problema.
Su iOS, è possibile utilizzare NSSetUncaughtExceptionHandler non solo per scrivere, ma anche per l'archiviazione temporanea in NSUserDefaults con il flag synchronize — ciò garantisce la persistenza anche in caso di terminazione immediata del processo.
Test obbligatorio — il Global Exception Handler deve essere testato in ogni fase del CI/CD. Su iOS, è possibile attivare un'eccezione di test tramite @throw NSException, su Android — tramite throw RuntimeException(). Verificare che il gestore venga chiamato, il log venga salvato e l'applicazione termini correttamente.
Secondo Google I/O 2023, più del 30% dei crash in produzione si verifica su dispositivi che lo sviluppatore non ha testato — diverse versioni di Android, firmware personalizzato, memoria limitata.
Il primo errore e il più comune — tentare di continuare l'esecuzione dell'applicazione dopo aver gestito un'eccezione. Dopo aver chiamato uncaughtException, l'applicazione è in uno stato instabile e qualsiasi ulteriore operazione può causare errori a cascata e danneggiamento dei dati.
Il secondo errore — eseguire operazioni lunghe all'interno del gestore. Le richieste di rete, la scrittura di file grandi o calcoli complessi non vengono completati prima della terminazione forzata del processo. Secondo Apple Technical Q&A QA1468, tentare di inviare una richiesta HTTP all'interno del gestore è la causa principale della perdita di report di crash.
Il terzo errore — ignorare i thread in background. Un Global Exception Handler impostato solo per il thread principale non protegge dai crash in coroutine, DispatchQueue, AsyncTask o RxJava. Su Android, ogni thread dovrebbe avere il proprio gestore — e setDefaultUncaughtExceptionHandler risolve questo solo per i thread senza un gestore individuale.
Il quarto errore — mancanza di fallback per i segnali del sistema operativo. NSSetUncaughtExceptionHandler su iOS non cattura SIGABRT, SIGSEGV e SIGBUS. Questi segnali richiedono l'impostazione separata di gestori tramite l'API sigaction. Gli sviluppatori lo scoprono solo quando l'app si blocca senza un singolo report di crash.
Il quinto errore — registrazione di dati riservati. Il log di crash può contenere email, token di autenticazione o dati personali degli utenti. Ciò viola il GDPR e le Linee guida per la revisione dell'App Store di Apple. Filtrare sempre i dati trasmessi utilizzando regex o una whitelist di campi consentiti.
Domande Frequenti
No — dopo l'invocazione del Global Exception Handler, lo stato dell'applicazione è indefinito. Qualsiasi tentativo di continuare l'esecuzione può portare al danneggiamento dei dati. L'unica azione corretta è salvare il log del crash e terminare il processo.
Non tutti — su iOS, NSSetUncaughtExceptionHandler cattura solo eccezioni Objective-C. Gli errori Swift e i segnali del sistema operativo (SIGSEGV, SIGABRT) richiedono gestori separati. Su Android, Thread.setDefaultUncaughtExceptionHandler cattura tutte le RuntimeExceptions ma non gli errori di codice nativo tramite JNI.
Salvare un riferimento al gestore precedente tramite Thread.getDefaultUncaughtExceptionHandler() prima di impostare il proprio. Alla fine del proprio gestore, chiamare previousHandler.uncaughtException(thread, throwable) — ciò garantisce che Crashlytics o Sentry ricevano i loro dati.
Per il codice nativo, è necessaria la gestione dei segnali tramite sigaction() — SIGSEGV, SIGABRT, SIGBUS. Su Android, è possibile utilizzare Google Breakpad o Crashpad. Su iOS a partire dalla versione 13, è disponibile la Signals API per la gestione delle eccezioni mach.
No — l'impostazione del gestore influisce solo sul momento in cui si verifica un'eccezione. Nel normale funzionamento dell'applicazione, non c'è overhead. L'unico rischio è una perdita di memoria se il gestore mantiene un riferimento a un'Activity o Context, impedendo la garbage collection.
Riepilogo
Svilupperemo un'applicazione mobile chiavi in mano
IT Sectr crea applicazioni iOS e Android per startup e aziende dal 2017. Ti consulteremo e ti proporremo la soluzione migliore.
Leggi anche