Global Exception Handler — un mecanism centralizat de interceptare a excepțiilor neprocesate, care previne închiderea bruscă a aplicației mobile. Conform datelor Apple Developer, 2024, gestionarea corectă a excepțiilor reduce numărul de crash-uri cu 40–60% și îmbunătățește experiența utilizatorului. Fără un astfel de handler, orice excepție neprocesată într-un fir de fundal duce la închiderea imediată a aplicației.
Principalele puncte
Global Exception Handler — este un mecanism centralizat de interceptare a excepțiilor care nu au fost gestionate la nivelul funcțiilor sau modulelor individuale ale aplicației. În contextul dezvoltării mobile, un astfel de handler reprezintă ultima linie de apărare înainte de terminarea forțată a procesului.
iOS și Android oferă API-uri integrate pentru instalarea unui handler global. Apple folosește NSSetUncaughtExceptionHandler pentru mediul Objective-C, iar Google oferă Thread.setDefaultUncaughtExceptionHandler în Java/Kotlin. Ambele mecanisme interceptează excepțiile care nu au fost prinse de constructiile try-catch în toate firele de execuție ale aplicației.
Conform datelor Crashlytics (Google, 2024), aproximativ 25% din crash-uri apar din cauza excepțiilor neprocesate în firele de fundal — un domeniu în care Global Exception Handler este deosebit de critic. Dezvoltatorii se concentrează adesea pe firul UI, uitând de operațiile asincrone.
Utilizarea handler-ului global nu înlocuiește gestionarea locală a erorilor, ci o completează. Sarcina principală este salvarea maximului de informații despre starea aplicației în momentul excepției și încheierea corectă a funcționării.
Mecanismul de funcționare al Global Exception Handler se bazează pe interceptarea semnalelor sistemului de operare sau a excepțiilor de timp de execuție. Când codul aruncă o excepție care nu este prinsă de niciun bloc try-catch, controlul este transferat handler-ului prestabilit.
Pe platforma iOS, handler-ul se înregistrează prin NSSetUncaughtExceptionHandler și primește obiectul NSException cu stack trace complet. Pe Android se folosește Thread.setDefaultUncaughtExceptionHandler, care primește Thread și Throwable — oferind acces la tipul excepției, mesaj și stiva de apeluri.
După primirea datelor despre crash, handler-ul execută trei acțiuni obligatorii: scrierea log-ului în stocarea locală, trimiterea raportului către Crashlytics sau Sentry și încheierea corectă a aplicației. Conform Apple WWDC 2023, timpul de lucru al handler-ului este limitat la 5 secunde — după aceasta, sistemul forțează terminarea procesului.
Pentru aplicațiile Swift începând cu iOS 13 a apărut Signals API, care procesează nu doar excepțiile, ci și semnalele sistemului de operare — SIGABRT, SIGSEGV și SIGBUS, extinzând zona de acoperire a handler-ului la erorile de memorie de nivel scăzut.
Implementarea handler-ului global pe iOS necesită setarea unei funcții C prin NSSetUncaughtExceptionHandler. Handler-ul este apelat sincron în momentul excepției neprocesate și primește contextul complet al erorii.
void handleUncaughtException(NSException exception) {
NSDictionary userInfo = [exception userInfo];
NSArray stackTrace = [exception callStackSymbols];
NSString reason = [exception reason];
// Salvăm log-ul de crash într-un fișier local
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);
}
O caracteristică importantă a implementării iOS: handler-ul interceptează doar excepțiile Objective-C. Erorile Swift, care folosesc mecanismul throw-catch, nu ajung la acest handler — pentru ele este necesară o procesare separată prin Swift Error Handling. Începând cu iOS 14, Apple recomandă combinarea NSSetUncaughtExceptionHandler cu Signals API pentru o acoperire maximă.
Conform Apple Technical Note TN2151, după apelarea handler-ului, aplicația trebuie să se încheie în termen de 5 secunde. Orice tentativă de a continua execuția după revenirea din handler duce la un comportament nedefinit și la un nou crash.
Android oferă un mecanism mai flexibil de gestionare globală a excepțiilor prin Thread.setDefaultUncaughtExceptionHandler. Handler-ul primește o referință către firul de execuție în care a apărut excepția și obiectul Throwable propriu-zis.
class GlobalExceptionHandler : Thread.UncaughtExceptionHandler {
override fun uncaughtException(thread: Thread, throwable: Throwable) {
// Salvăm log-ul de crash într-un fișier
val stackTrace = throwable.stackTraceToString()
val crashLog = "CRASH: ${thread.name}\n${stackTrace}"
val file = File(context.cacheDir, "crash_log.txt")
file.writeText(crashLog)
// Trimitem către Crashlytics
FirebaseCrashlytics.getInstance()
.recordException(throwable)
// Încheiem procesul
android.os.Process.killProcess(
android.os.Process.myPid()
)
}
}
// Instalare în Application.onCreate
class App : Application() {
override fun onCreate() {
super.onCreate()
Thread.setDefaultUncaughtExceptionHandler(
GlobalExceptionHandler()
)
}
}
Diferența cheie a implementării Android: fiecare fir de execuție are propriul handler, iar setDefaultUncaughtExceptionHandler setează handler-ul pentru toate firele care nu au un handler individual. Aceasta asigură acoperire globală — de la firul UI până la AsyncTask-urile de fundal și corutine.
Pe Android 12+ a apărut o limitare: după apelarea uncaughtException, aplicația trebuie să se încheie în termen de 100 de milisecunde. Dacă handler-ul execută operații îndelungate, sistemul poate ucide procesul înainte de finalizarea scrierii log-ului. Se recomandă utilizarea unui serviciu de fundal pentru trimiterea raportului de crash.
Prima regulă — nu încercați să restabiliți funcționarea aplicației după o excepție neprocesată. Starea aplicației după crash este nedefinită, iar continuarea lucrului poate duce la deteriorarea datelor utilizatorului.
Limitarea de timp — principala constrângere tehnică a Global Exception Handler. Pe iOS este 5 secunde, pe Android — 100 de milisecunde. În interiorul handler-ului este permisă doar salvarea unui set minim de date: tipul excepției, stiva de apeluri și starea câtorva variabile cheie.
Trimiterea cererilor de rețea, scrierea în baza de date și serializarea complexă trebuie mutate într-un mecanism amânat — de exemplu, salvarea log-ului într-un fișier și trimiterea acestuia la următoarea pornire a aplicației.
Serviciile de raportare crash — Firebase Crashlytics, Sentry, Bugsnag — își instalează singure handler-ul global. Dacă dezvoltatorul își instalează propriul handler peste ele, trebuie să transmită controlul sistemului de raportare crash după acțiunile sale. Pe Android se folosește compoziția handler-elor: se execută logica proprie, apoi se apelează handler-ul anterior.
Pentru Firebase Crashlytics se recomandă să nu se instaleze deloc un Thread.setDefaultUncaughtExceptionHandler personalizat — Crashlytics SDK o face automat la inițializare.
Contextul utilizatorului — pe lângă stiva standard de apeluri, este util să se logheze versiunea aplicației, versiunea sistemului de operare, cantitatea de memorie liberă și timpul de funcționare până la crash. Aceste date sunt esențiale pentru reproducerea și remedierea problemei.
În iOS se poate folosi NSSetUncaughtExceptionHandler nu doar pentru scriere, ci și pentru stocarea temporară a datelor în NSUserDefaults cu flag-ul synchronize — aceasta garantează salvarea datelor chiar și la terminarea imediată a procesului.
Testare obligatorie — Global Exception Handler trebuie testat în fiecare etapă a CI/CD. Pe iOS se poate iniția o excepție de test prin @throw NSException, pe Android — prin throw RuntimeException(). Se verifică că handler-ul este apelat, log-ul este salvat și aplicația se încheie corect.
Conform Google I/O 2023, peste 30% din crash-uri în producție apar pe dispozitive pe care dezvoltatorul nu le-a testat — versiuni diferite de Android, firmware personalizat, memorie limitată.
Prima și cea mai frecventă greșeală — încercarea de a continua execuția aplicației după procesarea excepției. După apelarea uncaughtException, aplicația se află într-o stare instabilă, iar orice operații ulterioare pot provoca cascada de erori și deteriorarea datelor.
A doua greșeală — executarea de operații lungi în interiorul handler-ului. Cererile de rețea, scrierea fișierelor mari sau calculele complexe nu au timp să se finalizeze înainte de terminarea forțată a procesului. Conform Apple Technical Q&A QA1468, încercarea de a trimite o cerere HTTP în interiorul handler-ului este principala cauză a rapoartelor de crash pierdute.
A treia greșeală — ignorarea firelor de fundal. Global Exception Handler, setat doar pentru firul principal, nu protejează împotriva crash-urilor în corutine, DispatchQueue, AsyncTask sau RxJava. Pe Android, fiecare fir de execuție ar trebui să aibă propriul handler — iar setDefaultUncaughtExceptionHandler rezolvă această problemă doar pentru firele fără handler individual.
A patra greșeală — lipsa unui fallback pentru semnalele sistemului de operare. NSSetUncaughtExceptionHandler pe iOS nu interceptează SIGABRT, SIGSEGV și SIGBUS. Pentru aceste semnale este necesară instalarea separată de handler-e prin sigaction API. Dezvoltatorii află despre aceasta doar când aplicația se prăbușește fără niciun raport de crash.
A cincea greșeală — logarea datelor confidențiale. În log-ul de crash ajung emailuri, token-uri de autorizare sau date personale ale utilizatorilor. Aceasta încalcă GDPR și Apple App Store Review Guidelines. Filtrați întotdeauna datele transmise prin regex sau o listă whitelist de câmpuri permise.
Întrebări frecvente
Nu — după apelarea Global Exception Handler, starea aplicației este nedefinită. Orice încercare de a continua poate duce la deteriorarea datelor. Singura acțiune corectă — salvați log-ul de crash și încheiați procesul.
Nu toate — pe iOS, NSSetUncaughtExceptionHandler prinde doar excepțiile Objective-C. Erorile Swift și semnalele sistemului de operare (SIGSEGV, SIGABRT) necesită handler-e separate. Pe Android, Thread.setDefaultUncaughtExceptionHandler interceptează toate RuntimeException, dar nu și erorile de cod native prin JNI.
Salvați referința către handler-ul anterior prin Thread.getDefaultUncaughtExceptionHandler() înainte de a vă instala propriul handler. La sfârșitul handler-ului dvs., apelați previousHandler.uncaughtException(thread, throwable) — aceasta garantează că Crashlytics sau Sentry își vor primi datele.
Pentru codul nativ este necesară gestionarea semnalelor prin sigaction() — SIGSEGV, SIGABRT, SIGBUS. Pe Android se poate utiliza Google Breakpad sau Crashpad. Pe iOS, începând cu versiunea 13, este disponibil Signals API pentru gestionarea excepțiilor mach.
Nu — instalarea handler-ului afectează doar momentul apariției excepției. În funcționarea normală a aplicației nu există overhead. Singurul risc este scurgerea de memorie dacă handler-ul păstrează o referință către Activity sau Context, împiedicând colectarea lor de gunoi.
Concluzii
Vom dezvolta o aplicație mobilă la cheie
IT Sectr creează aplicații iOS și Android pentru startup-uri și afaceri din 2017. Vă vom consilia și vă vom propune cea mai bună soluție.
Citiți și