Global Exception Handler — mechanizm centralnego przechwytywania nieobsłużonych wyjątków, zapobiegający awaryjnemu zamykaniu aplikacji mobilnej. Według danych Apple Developer, 2024, prawidłowa obsługa wyjątków zmniejsza liczbę crashy o 40–60% i poprawia doświadczenia użytkownika. Bez takiego handlera każdy nieobsłużony wyjątek w wątku tła prowadzi do natychmiastowego zamknięcia aplikacji.
Najważniejsze
Global Exception Handler — to centralny mechanizm przechwytywania wyjątków, które nie zostały obsłużone na poziomie poszczególnych funkcji lub modułów aplikacji. W kontekście programowania mobilnego taki handler stanowi ostatnią linię obrony przed awaryjnym zakończeniem procesu.
iOS i Android udostępniają wbudowane API do ustawienia globalnego handlera. Apple używa NSSetUncaughtExceptionHandler dla środowiska Objective-C, a Google oferuje Thread.setDefaultUncaughtExceptionHandler w Java/Kotlin. Oba mechanizmy przechwytują wyjątki, które nie zostały złapane przez konstrukcje try-catch we wszystkich wątkach aplikacji.
Według danych Crashlytics (Google, 2024), około 25% crashy występuje z powodu nieobsłużonych wyjątków w wątkach tła — obszar, w którym Global Exception Handler jest szczególnie krytyczny. Deweloperzy często skupiają się na wątku UI, zapominając o operacjach asynchronicznych.
Używanie globalnego handlera nie zastępuje lokalnej obsługi błędów, ale ją uzupełnia. Głównym zadaniem jest zapisanie maksimum informacji o stanie aplikacji w momencie wyjątku i poprawne zakończenie działania.
Mechanizm działania Global Exception Handler opiera się na przechwytywaniu sygnałów systemu operacyjnego lub wyjątków czasu wykonania. Gdy kod zgłasza wyjątek, który nie został złapany przez żaden blok try-catch, sterowanie przekazywane jest do wcześniej ustawionego handlera.
Na platformie iOS handler rejestruje się przez NSSetUncaughtExceptionHandler i otrzymuje obiekt NSException z pełnym stack tracem. Na Android używa się Thread.setDefaultUncaughtExceptionHandler, który przyjmuje Thread i Throwable — daje to dostęp do typu wyjątku, komunikatu i stosu wywołań.
Po otrzymaniu danych o crashu handler wykonuje trzy obowiązkowe czynności: zapis logu w lokalnym magazynie, wysłanie raportu do Crashlytics lub Sentry oraz poprawne zakończenie aplikacji. Według danych Apple WWDC 2023, czas działania handlera jest ograniczony do 5 sekund — po tym czasie system przymusowo kończy proces.
Dla aplikacji Swift od iOS 13 pojawił się Signals API, który obsługuje nie tylko wyjątki, ale także sygnały systemu operacyjnego — SIGABRT, SIGSEGV i SIGBUS, rozszerzając strefę pokrycia handlera na niskopoziomowe błędy pamięci.
Implementacja globalnego handlera na iOS wymaga ustawienia funkcji C przez NSSetUncaughtExceptionHandler. Handler jest wywoływany synchronicznie w momencie nieobsłużonego wyjątku i otrzymuje pełny kontekst błędu.
void handleUncaughtException(NSException exception) {
NSDictionary userInfo = [exception userInfo];
NSArray stackTrace = [exception callStackSymbols];
NSString reason = [exception reason];
// Zapisujemy log crash do pliku lokalnego
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);
}
Ważna cecha implementacji na iOS: handler przechwytuje tylko wyjątki Objective-C. Błędy Swifta, używające mechanizmu throw-catch, nie trafiają do tego handlera — wymagają one osobnej obsługi przez Swift Error Handling. Od iOS 14 Apple zaleca łączenie NSSetUncaughtExceptionHandler z Signals API dla maksymalnego pokrycia.
Według Apple Technical Note TN2151, po wywołaniu handlera aplikacja powinna zakończyć działanie w ciągu 5 sekund. Każda próba kontynuowania wykonania po powrocie z handlera prowadzi do nieokreślonego zachowania i ponownego crasha.
Android oferuje bardziej elastyczny mechanizm globalnej obsługi wyjątków przez Thread.setDefaultUncaughtExceptionHandler. Handler otrzymuje referencję do wątku, w którym wystąpił wyjątek, oraz sam obiekt Throwable.
class GlobalExceptionHandler : Thread.UncaughtExceptionHandler {
override fun uncaughtException(thread: Thread, throwable: Throwable) {
// Zapisujemy log crash do pliku
val stackTrace = throwable.stackTraceToString()
val crashLog = "CRASH: ${thread.name}\n${stackTrace}"
val file = File(context.cacheDir, "crash_log.txt")
file.writeText(crashLog)
// Wysyłamy do Crashlytics
FirebaseCrashlytics.getInstance()
.recordException(throwable)
// Kończymy proces
android.os.Process.killProcess(
android.os.Process.myPid()
)
}
}
// Instalacja w Application.onCreate
class App : Application() {
override fun onCreate() {
super.onCreate()
Thread.setDefaultUncaughtExceptionHandler(
GlobalExceptionHandler()
)
}
}
Kluczowa różnica implementacji na Android: każdy wątek ma własny handler, a setDefaultUncaughtExceptionHandler ustawia handler dla wszystkich wątków, dla których nie ustawiono indywidualnego. Zapewnia to globalne pokrycie — od wątku UI po tła AsyncTask i korutyny.
Na Android 12+ pojawiło się ograniczenie: po wywołaniu uncaughtException aplikacja musi zakończyć działanie w ciągu 100 milisekund. Jeśli handler wykonuje długotrwałe operacje, system może zabić proces przed zakończeniem zapisu logu. Zaleca się używanie usługi tła do wysyłania raportu crash.
Pierwsza zasada — nie próbować przywracać działania aplikacji po nieobsłużonym wyjątku. Stan aplikacji po crashu jest nieokreślony, a kontynuowanie może prowadzić do uszkodzenia danych użytkownika.
Ograniczenie czasowe — główne techniczne ograniczenie Global Exception Handler. Na iOS to 5 sekund, na Android — 100 milisekund. Wewnątrz handlera dopuszczalne jest tylko zapisanie minimalnego zestawu danych: typ wyjątku, stos wywołań i stan kilku kluczowych zmiennych.
Wysyłanie zapytań sieciowych, zapis do bazy danych i złożoną serializację należy przenieść do mechanizmu odroczonego — na przykład zapisać log do pliku i wysłać go przy następnym uruchomieniu aplikacji.
Serwisy raportowania crash — Firebase Crashlytics, Sentry, Bugsnag — same ustawiają globalny handler. Jeśli deweloper ustawia własny handler na wierzchu, należy przekazać sterowanie do systemu raportowania crash po swoich działaniach. Na Android używa się kompozycji handlerów: wykonać własną logikę, następnie wywołać poprzedni handler.
Dla Firebase Crashlytics zaleca się w ogóle nie ustawiać własnego Thread.setDefaultUncaughtExceptionHandler — Crashlytics SDK robi to automatycznie przy inicjalizacji.
Kontekst użytkownika — oprócz standardowego stosu wywołań, warto logować wersję aplikacji, wersję systemu operacyjnego, ilość wolnej pamięci i czas pracy do crasha. Te dane są krytyczne dla odtworzenia i rozwiązania problemu.
W iOS można użyć NSSetUncaughtExceptionHandler nie tylko do zapisu, ale także do tymczasowego przechowywania danych w NSUserDefaults z flagą synchronize — gwarantuje to zachowanie danych nawet przy natychmiastowym zakończeniu procesu.
Obowiązkowe testowanie — Global Exception Handler powinien być testowany na każdym etapie CI/CD. Na iOS można zainicjować testowy wyjątek przez @throw NSException, na Android — przez throw RuntimeException(). Sprawdza się, czy handler jest wywoływany, log jest zapisywany i aplikacja poprawnie się kończy.
Według danych Google I/O 2023, ponad 30% crashy w produkcji występuje na urządzeniach, których deweloper nie testował — różne wersje Androida, niestandardowe oprogramowanie, ograniczona pamięć.
Pierwszy i najczęstszy błąd — próba kontynuowania działania aplikacji po obsłudze wyjątku. Po wywołaniu uncaughtException aplikacja znajduje się w niestabilnym stanie, a wszelkie dalsze operacje mogą spowodować kaskadę błędów i uszkodzenie danych.
Drugi błąd — wykonywanie długotrwałych operacji wewnątrz handlera. Zapytania sieciowe, zapis dużych plików czy złożone obliczenia nie zdążą się zakończyć przed przymusowym zakończeniem procesu. Według Apple Technical Q&A QA1468, próba wysłania zapytania HTTP wewnątrz handlera to główna przyczyna utraconych raportów crash.
Trzeci błąd — ignorowanie wątków tła. Global Exception Handler ustawiony tylko dla głównego wątku nie chroni przed crashami w korutynach, DispatchQueue, AsyncTask czy RxJava. Na Android każdy wątek powinien mieć własny handler — a setDefaultUncaughtExceptionHandler rozwiązuje ten problem tylko dla wątków bez indywidualnego handlera.
Czwarty błąd — brak fallbacka dla sygnałów systemu operacyjnego. NSSetUncaughtExceptionHandler na iOS nie przechwytuje SIGABRT, SIGSEGV i SIGBUS. Dla tych sygnałów wymagane jest osobne ustawienie handlerów przez sigaction API. Deweloperzy dowiadują się o tym dopiero, gdy aplikacja pada bez żadnego raportu crash.
Piąty błąd — logowanie poufnych danych. Do logu crash trafiają emaile, tokeny autoryzacji lub dane osobowe użytkowników. Narusza to RODO i Apple App Store Review Guidelines. Zawsze filtruj przekazywane dane przez regex lub whitelist dozwolonych pól.
Często zadawane pytania
Nie — po wywołaniu Global Exception Handler stan aplikacji jest nieokreślony. Każda próba kontynuowania pracy może prowadzić do uszkodzenia danych. Jedynym poprawnym działaniem jest zapisanie logu crash i zakończenie procesu.
Nie wszystkie — na iOS NSSetUncaughtExceptionHandler łapie tylko wyjątki Objective-C. Błędy Swifta i sygnały systemu operacyjnego (SIGSEGV, SIGABRT) wymagają osobnych handlerów. Na Android Thread.setDefaultUncaughtExceptionHandler przechwytuje wszystkie RuntimeException, ale nie błędy kodu native przez JNI.
Zapisz referencję do poprzedniego handlera przez Thread.getDefaultUncaughtExceptionHandler() przed ustawieniem swojego. Na końcu swojego handlera wywołaj previousHandler.uncaughtException(thread, throwable) — gwarantuje to, że Crashlytics lub Sentry otrzymają swoje dane.
Dla kodu native wymagana jest obsługa sygnałów przez sigaction() — SIGSEGV, SIGABRT, SIGBUS. Na Android można użyć Google Breakpad lub Crashpad. Na iOS od wersji 13 pojawił się Signals API do obsługi mach-wyjątków.
Nie — ustawienie handlera wpływa tylko na moment wystąpienia wyjątku. W normalnej pracy aplikacji nie ma narzutu. Jedynym ryzykiem jest wyciek pamięci, jeśli handler przechowuje referencję do Activity lub Context, uniemożliwiając ich zwolnienie przez garbage collector.
Podsumowanie
Opracujemy aplikację mobilną pod klucz
IT Sectr tworzy aplikacje na iOS i Androida dla startupów i firm od 2017 roku. Doradzimy Ci i zaproponujemy najlepsze rozwiązanie.
Przeczytaj również