Global Exception Handler — mechanismus centralizovaného zachycení neošetřených výjimek, který zabraňuje havarijnímu ukončení mobilní aplikace. Podle údajů Apple Developer, 2024 snižuje správné zpracování výjimek počet crashů o 40–60% a zlepšuje uživatelský zážitek. Bez takového handleru vede každá neošetřená výjimka na vlákně na pozadí k okamžitému ukončení aplikace.
Hlavní body
Global Exception Handler — je centralizovaný mechanismus pro zachycení výjimek, které nebyly ošetřeny na úrovni jednotlivých funkcí nebo modulů aplikace. V kontextu mobilního vývoje slouží takový handler jako poslední obranná linie před havarijním ukončením procesu.
iOS a Android poskytují vestavěná API pro nastavení globálního handleru. Apple používá NSSetUncaughtExceptionHandler pro prostředí Objective-C a Google nabízí Thread.setDefaultUncaughtExceptionHandler v Javě/Kotlinu. Oba mechanismy zachycují výjimky, které nebyly zachyceny konstrukcemi try-catch ve všech vláknech aplikace.
Podle údajů Crashlytics (Google, 2024) dochází k přibližně 25% crashů kvůli neošetřeným výjimkám na vláknech na pozadí — oblast, kde je Global Exception Handler obzvláště kritický. Vývojáři se často zaměřují na UI vlákno a zapomínají na asynchronní operace.
Použití globálního handleru nenahrazuje místní zpracování chyb, ale doplňuje jej. Hlavním úkolem je uložit maximum informací o stavu aplikace v okamžiku výjimky a správně ukončit činnost.
Mechanismus fungování Global Exception Handler je založen na zachycování signálů operačního systému nebo výjimek běhového prostředí. Když kód vyvolá výjimku, která není zachycena žádným blokem try-catch, řízení je předáno předem nastavenému handleru.
Na platformě iOS se handler registruje prostřednictvím NSSetUncaughtExceptionHandler a obdrží objekt NSException s úplným stack trace. Na Androidu se používá Thread.setDefaultUncaughtExceptionHandler, který přijímá Thread a Throwable — to poskytuje přístup k typu výjimky, zprávě a zásobníku volání.
Po obdržení dat o crashu handler provede tři povinné akce: zápis protokolu do místního úložiště, odeslání zprávy do Crashlytics nebo Sentry a správné ukončení aplikace. Podle Apple WWDC 2023 je doba práce handleru omezena na 5 sekund — poté systém proces násilně ukončí.
Pro aplikace Swift od iOS 13 se objevilo Signals API, které zpracovává nejen výjimky, ale také signály operačního systému — SIGABRT, SIGSEGV a SIGBUS, čímž rozšiřuje pokrytí handleru na nízkoúrovňové chyby paměti.
Implementace globálního handleru na iOS vyžaduje nastavení funkce C prostřednictvím NSSetUncaughtExceptionHandler. Handler je volán synchronně v okamžiku neošetřené výjimky a obdrží úplný kontext chyby.
void handleUncaughtException(NSException exception) {
NSDictionary userInfo = [exception userInfo];
NSArray stackTrace = [exception callStackSymbols];
NSString reason = [exception reason];
// Uložit crash protokol do místního souboru
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);
}
Důležitá vlastnost implementace na iOS: handler zachycuje pouze výjimky Objective-C. Chyby Swiftu, které používají mechanismus throw-catch, se do tohoto handleru nedostanou — vyžadují samostatné zpracování pomocí Swift Error Handling. Od iOS 14 Apple doporučuje kombinovat NSSetUncaughtExceptionHandler s Signals API pro maximální pokrytí.
Podle Apple Technical Note TN2151 musí být aplikace po volání handleru ukončena do 5 sekund. Jakýkoli pokus o pokračování provádění po návratu z handleru vede k nedefinovanému chování a opětovnému crashu.
Android poskytuje flexibilnější mechanismus globálního zpracování výjimek prostřednictvím Thread.setDefaultUncaughtExceptionHandler. Handler obdrží odkaz na vlákno, ve kterém došlo k výjimce, a samotný objekt Throwable.
class GlobalExceptionHandler : Thread.UncaughtExceptionHandler {
override fun uncaughtException(thread: Thread, throwable: Throwable) {
// Uložit crash protokol do souboru
val stackTrace = throwable.stackTraceToString()
val crashLog = "CRASH: ${thread.name}\n${stackTrace}"
val file = File(context.cacheDir, "crash_log.txt")
file.writeText(crashLog)
// Odeslat do Crashlytics
FirebaseCrashlytics.getInstance()
.recordException(throwable)
// Ukončit proces
android.os.Process.killProcess(
android.os.Process.myPid()
)
}
}
// Instalace v Application.onCreate
class App : Application() {
override fun onCreate() {
super.onCreate()
Thread.setDefaultUncaughtExceptionHandler(
GlobalExceptionHandler()
)
}
}
Klíčový rozdíl implementace na Android: každé vlákno má svůj vlastní handler a setDefaultUncaughtExceptionHandler nastaví handler pro všechna vlákna, která nemají individuální handler. To zajišťuje globální pokrytí — od UI vlákna po vlákna na pozadí AsyncTask a korutiny.
Na Android 12+ se objevilo omezení: po volání uncaughtException musí být aplikace ukončena do 100 milisekund. Pokud handler provádí dlouhodobé operace, systém může zabít proces před dokončením zápisu protokolu. Doporučuje se používat službu na pozadí pro odeslání crash zprávy.
První pravidlo — nepokoušejte se obnovit aplikaci po neošetřené výjimce. Stav aplikace po crashu je nedefinovaný a pokračování v práci může vést k poškození uživatelských dat.
Časové omezení — hlavní technické omezení Global Exception Handler. Na iOS je to 5 sekund, na Android — 100 milisekund. Uvnitř handleru je povoleno pouze uložit minimální sadu dat: typ výjimky, zásobník volání a stav několika klíčových proměnných.
Odesílání síťových požadavků, zápis do databáze a složitou serializaci je třeba přesunout do odloženého mechanismu — například uložit protokol do souboru a odeslat jej při příštím spuštění aplikace.
Služby pro hlášení crash — Firebase Crashlytics, Sentry, Bugsnag — samy nastavují globální handler. Pokud vývojář nastaví svůj vlastní handler nad ně, musí po svých akcích předat řízení systému pro hlášení crash. Na Androidu se používá kompozice handlerů: provést vlastní logiku, poté zavolat předchozí handler.
Pro Firebase Crashlytics se doporučuje vůbec nenastavovat vlastní Thread.setDefaultUncaughtExceptionHandler — Crashlytics SDK to dělá automaticky při inicializaci.
Uživatelský kontext — kromě standardního zásobníku volání je užitečné protokolovat verzi aplikace, verzi OS, množství volné paměti a dobu běhu do crashu. Tato data jsou kritická pro reprodukci a odstranění problému.
Na iOS lze NSSetUncaughtExceptionHandler použít nejen k zápisu, ale také k dočasnému ukládání dat do NSUserDefaults s příznakem synchronize — to zaručuje uchování dat i při okamžitém ukončení procesu.
Povinné testování — Global Exception Handler musí být testován v každé fázi CI/CD. Na iOS lze iniciovat testovací výjimku pomocí @throw NSException, na Android — pomocí throw RuntimeException(). Ověřuje se, že je handler volán, protokol je uložen a aplikace je správně ukončena.
Podle Google I/O 2023 dochází k více než 30% crashů v produkci na zařízeních, která vývojář netestoval — různé verze Androidu, vlastní firmware, omezená paměť.
První a nejčastější chyba — pokus o pokračování provádění aplikace po zpracování výjimky. Po volání uncaughtException je aplikace v nestabilním stavu a jakékoli další operace mohou způsobit kaskádu chyb a poškození dat.
Druhá chyba — provádění dlouhodobých operací uvnitř handleru. Síťové požadavky, zápis velkých souborů nebo složité výpočty nestihnou dokončit před násilným ukončením procesu. Podle Apple Technical Q&A QA1468 je pokus o odeslání HTTP požadavku uvnitř handleru hlavní příčinou ztracených crash zpráv.
Třetí chyba — ignorování vláken na pozadí. Global Exception Handler nastavený pouze pro hlavní vlákno nechrání před crashy v korutinách, DispatchQueue, AsyncTask nebo RxJava. Na Androidu by každé vlákno mělo mít svůj vlastní handler — a setDefaultUncaughtExceptionHandler řeší tento problém pouze pro vlákna bez individuálního handleru.
Čtvrtá chyba — chybějící fallback pro signály operačního systému. NSSetUncaughtExceptionHandler na iOS nezachycuje SIGABRT, SIGSEGV a SIGBUS. Pro tyto signály je vyžadováno samostatné nastavení handlerů prostřednictvím sigaction API. Vývojáři se o tom dozví, až když aplikace spadne bez jediné crash zprávy.
Pátá chyba — protokolování důvěrných údajů. Do crash protokolu se dostanou e-maily, autorizační tokeny nebo osobní údaje uživatelů. To porušuje GDPR a Apple App Store Review Guidelines. Vždy filtrujte předávaná data pomocí regexu nebo whitelistu povolených polí.
Často kladené otázky
Ne — po volání Global Exception Handler je stav aplikace nedefinovaný. Jakýkoli pokus o pokračování v práci může vést k poškození dat. Jediným správným krokem je uložit crash protokol a ukončit proces.
Ne všechny — na iOS NSSetUncaughtExceptionHandler chytá pouze výjimky Objective-C. Chyby Swiftu a signály OS (SIGSEGV, SIGABRT) vyžadují samostatné handlery. Na Androidu Thread.setDefaultUncaughtExceptionHandler zachycuje všechny RuntimeException, ale ne chyby nativního kódu přes JNI.
Uložte odkaz na předchozí handler prostřednictvím Thread.getDefaultUncaughtExceptionHandler() před nastavením vlastního handleru. Na konci svého handleru zavolejte previousHandler.uncaughtException(thread, throwable) — to zaručuje, že Crashlytics nebo Sentry obdrží svá data.
Pro nativní kód je vyžadováno zpracování signálů prostřednictvím sigaction() — SIGSEGV, SIGABRT, SIGBUS. Na Androidu lze použít Google Breakpad nebo Crashpad. Na iOS od verze 13 je k dispozici Signals API pro zpracování mach výjimek.
Ne — nastavení handleru ovlivňuje pouze okamžik vzniku výjimky. Při normálním provozu aplikace nedochází k žádné režii. Jediným rizikem je únik paměti, pokud handler uchovává odkaz na Activity nebo Context, čímž brání jejich uvolnění garbage collectorem.
Shrnutí
Vyvineme mobilní aplikaci na klíč
IT Sectr vytváří aplikace pro iOS a Android pro startupy a podniky od roku 2017. Poradíme vám a navrhneme nejlepší řešení.
Přečtěte si také