Global Exception Handler — a kezeletlen kivételek központosított elfogásának mechanizmusa, amely megakadályozza a mobilalkalmazás váratlan leállását. A Apple Developer, 2024 adatai szerint a kivételek helyes kezelése 40–60%-kal csökkenti a crash-ek számát és javítja a felhasználói élményt. Ilyen kezelő nélkül bármely kezeletlen kivétel egy háttérszálon az alkalmazás azonnali bezárásához vezet.
Főbb pontok
Global Exception Handler — egy központosított mechanizmus azon kivételek elfogására, amelyeket nem kezeltek az alkalmazás egyes függvényeinek vagy moduljainak szintjén. A mobilfejlesztés kontextusában az ilyen kezelő az utolsó védelmi vonalként szolgál a folyamat kényszerített megszakítása előtt.
Az iOS és Android beépített API-kat biztosítanak a globális kezelő beállításához. Az Apple az NSSetUncaughtExceptionHandler-t használja az Objective-C környezethez, a Google pedig a Thread.setDefaultUncaughtExceptionHandler-t kínálja Java/Kotlin nyelven. Mindkét mechanizmus elkapja azokat a kivételeket, amelyeket a try-catch szerkezetek nem fogtak el az alkalmazás összes szálán.
A Crashlytics (Google, 2024) adatai szerint a crash-ek körülbelül 25%-a kezeletlen kivételek miatt következik be háttérszálakon — ez az a terület, ahol a Global Exception Handler különösen kritikus. A fejlesztők gyakran az UI szálra összpontosítanak, megfeledkezve az aszinkron műveletekről.
A globális kezelő használata nem helyettesíti a helyi hibakezelést, hanem kiegészíti azt. A fő feladat — maximális információ mentése az alkalmazás állapotáról a kivétel pillanatában és a munka helyes befejezése.
Működési mechanizmus — a Global Exception Handler az operációs rendszer jeleinek vagy futásidejű kivételek elfogásán alapul. Amikor a kód olyan kivételt dob, amelyet egyetlen try-catch blokk sem fogott el, a vezérlés átadódik az előre beállított kezelőnek.
Az iOS platformon a kezelő az NSSetUncaughtExceptionHandler-en keresztül regisztrálódik, és egy NSException objektumot kap teljes stack trace-szel. Androidon a Thread.setDefaultUncaughtExceptionHandler-t használják, amely Thread-et és Throwable-t fogad — ez hozzáférést biztosít a kivétel típusához, üzenethez és hívási veremhez.
A crash adatok fogadása után a kezelő három kötelező műveletet hajt végre: napló írása a helyi tárolóba, jelentés küldése a Crashlytics vagy Sentry részére, és az alkalmazás helyes befejezése. Az Apple WWDC 2023 szerint a kezelő munkaideje 5 másodpercre korlátozódik — ezt követően a rendszer kényszerítve befejezi a folyamatot.
Az iOS 13-tól kezdve a Swift alkalmazásokhoz megjelent a Signals API, amely nemcsak a kivételeket, hanem az operációs rendszer jeleit is feldolgozza — SIGABRT, SIGSEGV és SIGBUS — ezzel kiterjesztve a kezelő lefedettségét az alacsony szintű memóriahibákra.
Megvalósítás — a globális kezelő iOS-en való beállításához C függvény beállítása szükséges az NSSetUncaughtExceptionHandler-en keresztül. A kezelő szinkron módon hívódik meg egy kezeletlen kivétel pillanatában, és megkapja a teljes hiba kontextust.
void handleUncaughtException(NSException exception) {
NSDictionary userInfo = [exception userInfo];
NSArray stackTrace = [exception callStackSymbols];
NSString reason = [exception reason];
// Crash napló mentése helyi fájlba
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);
}
Fontos jellemzője az iOS megvalósításnak: a kezelő csak Objective-C kivételeket fog el. A Swift hibák, amelyek a throw-catch mechanizmust használják, nem jutnak el ehhez a kezelőhöz — számukra külön feldolgozás szükséges a Swift Error Handling-en keresztül. iOS 14-től az Apple a NSSetUncaughtExceptionHandler és a Signals API kombinálását ajánlja a maximális lefedettség érdekében.
A Apple Technical Note TN2151 szerint a kezelő meghívása után az alkalmazásnak 5 másodpercen belül be kell fejeződnie. Bármilyen kísérlet a végrehajtás folytatására a kezelőből való visszatérés után meghatározatlan viselkedéshez és ismételt crash-hez vezet.
Android rugalmasabb mechanizmust kínál a globális kivételkezeléshez a Thread.setDefaultUncaughtExceptionHandler-en keresztül. A kezelő megkapja a hivatkozást arra a szálra, amelyben a kivétel történt, és magát a Throwable objektumot.
class GlobalExceptionHandler : Thread.UncaughtExceptionHandler {
override fun uncaughtException(thread: Thread, throwable: Throwable) {
// Crash napló mentése fájlba
val stackTrace = throwable.stackTraceToString()
val crashLog = "CRASH: ${thread.name}\n${stackTrace}"
val file = File(context.cacheDir, "crash_log.txt")
file.writeText(crashLog)
// Küldés Crashlytics-be
FirebaseCrashlytics.getInstance()
.recordException(throwable)
// Folyamat befejezése
android.os.Process.killProcess(
android.os.Process.myPid()
)
}
}
// Telepítés az Application.onCreate-ben
class App : Application() {
override fun onCreate() {
super.onCreate()
Thread.setDefaultUncaughtExceptionHandler(
GlobalExceptionHandler()
)
}
}
Fő különbség az Android megvalósításban: minden szálnak saját kezelője van, és a setDefaultUncaughtExceptionHandler azon szálak számára állít be kezelőt, amelyeknek nincs egyedi kezelőjük. Ez globális lefedettséget biztosít — az UI száltól a háttérbeli AsyncTask-ig és korutinokig.
Android 12+-tól kezdve korlátozás lépett életbe: az uncaughtException meghívása után az alkalmazásnak 100 ezredmásodpercen belül be kell fejeződnie. Ha a kezelő hosszú ideig tartó műveleteket hajt végre, a rendszer megölheti a folyamatot a naplózás befejezése előtt. Javasolt háttérszolgáltatás használata a crash jelentés küldéséhez.
Első szabály — ne próbálja helyreállítani az alkalmazást egy kezeletlen kivétel után. Az alkalmazás állapota crash után meghatározatlan, és a munka folytatása a felhasználói adatok sérüléséhez vezethet.
Időkorlát — a Global Exception Handler fő technikai korlátozása. iOS-en ez 5 másodperc, Androidon — 100 ezredmásodperc. A kezelőn belül csak minimális adatkészlet mentése engedélyezett: a kivétel típusa, a hívási verem és néhány kulcsváltozó állapota.
A hálózati kérések küldését, adatbázisba írást és összetett szerializációt halasztott mechanizmusba kell áthelyezni — például a napló mentése fájlba, majd elküldése az alkalmazás következő indításakor.
Crash-reporting szolgáltatások — Firebase Crashlytics, Sentry, Bugsnag — maguk állítják be a globális kezelőt. Ha a fejlesztő saját kezelőt telepít föléjük, a saját műveletei után át kell adnia a vezérlést a crash-reporting rendszernek. Androidon kezelő kompozíciót használnak: saját logika végrehajtása, majd az előző kezelő meghívása.
A Firebase Crashlytics esetében ajánlott egyáltalán nem telepíteni egyéni Thread.setDefaultUncaughtExceptionHandler-t — a Crashlytics SDK ezt automatikusan elvégzi az inicializáláskor.
Felhasználói kontextus — a szokásos hívási vermen kívül érdemes naplózni az alkalmazás verzióját, az operációs rendszer verzióját, a szabad memória mennyiségét és a crash-ig eltelt munkaidőt. Ezek az adatok kritikus fontosságúak a probléma reprodukálásához és megoldásához.
iOS-en az NSSetUncaughtExceptionHandler nemcsak naplózáshoz használható, hanem adatok ideiglenes tárolására is az NSUserDefaults-ban synchronize jelzővel — ez garantálja az adatok megőrzését még a folyamat azonnali befejezése esetén is.
Kötelező tesztelés — a Global Exception Handler-t a CI/CD minden szakaszában tesztelni kell. iOS-en teszt kivétel indítható a @throw NSException segítségével, Androidon a throw RuntimeException() segítségével. Ellenőrizni kell, hogy a kezelő meghívódik, a napló elmentődik és az alkalmazás helyesen befejeződik.
A Google I/O 2023 szerint a termelésben előforduló crash-ek több mint 30%-a olyan eszközökön történik, amelyeket a fejlesztő nem tesztelt — különböző Android verziók, egyedi firmware, korlátozott memória.
Első és leggyakoribb hiba — az alkalmazás végrehajtásának folytatása a kivétel feldolgozása után. Az uncaughtException meghívása után az alkalmazás instabil állapotban van, és minden további művelet hibák kaszkádját és adatsérülést okozhat.
Második hiba — hosszú ideig tartó műveletek végrehajtása a kezelőn belül. Hálózati kérések, nagy fájlok írása vagy összetett számítások nem fejeződnek be a folyamat kényszerített befejezése előtt. Az Apple Technical Q&A QA1468 szerint a HTTP kérés küldésére tett kísérlet a kezelőn belül a vezető oka az elveszett crash jelentéseknek.
Harmadik hiba — a háttérszálak figyelmen kívül hagyása. A csak a főszálra beállított Global Exception Handler nem véd a crash-ektől a korutinokban, DispatchQueue-ban, AsyncTask-ben vagy RxJava-ban. Androidon minden szálnak saját kezelővel kell rendelkeznie — a setDefaultUncaughtExceptionHandler csak azon szálak esetében oldja meg ezt a problémát, amelyeknek nincs egyedi kezelőjük.
Negyedik hiba — az operációs rendszer jeleire vonatkozó tartalék megoldás hiánya. Az NSSetUncaughtExceptionHandler iOS-en nem fogja el a SIGABRT, SIGSEGV és SIGBUS jeleket. Ezekhez a jelekhez külön kezelők telepítése szükséges a sigaction API-n keresztül. A fejlesztők csak akkor szembesülnek ezzel, amikor az alkalmazás crash-el anélkül, hogy bármilyen crash jelentés készülne.
Ötödik hiba — bizalmas adatok naplózása. A crash naplóba e-mailek, hitelesítési tokenek vagy felhasználók személyes adatai kerülnek. Ez sérti a GDPR-t és az Apple App Store Review Guidelines-t. Mindig szűrje a továbbított adatokat regex vagy engedélyezett mezők whitelist listája segítségével.
Gyakran Ismételt Kérdések
Nem — a Global Exception Handler meghívása után az alkalmazás állapota meghatározatlan. Bármilyen kísérlet a munka folytatására adatsérüléshez vezethet. Az egyetlen helyes művelet — a crash napló mentése és a folyamat befejezése.
Nem mindet — iOS-en az NSSetUncaughtExceptionHandler csak az Objective-C kivételeket fogja el. A Swift hibák és az operációs rendszer jelei (SIGSEGV, SIGABRT) külön kezelőket igényelnek. Androidon a Thread.setDefaultUncaughtExceptionHandler az összes RuntimeException-t elkapja, de a JNI-n keresztüli natív kódhibákat nem.
Mentse el a hivatkozást az előző kezelőre a Thread.getDefaultUncaughtExceptionHandler()-en keresztül a saját kezelő telepítése előtt. A kezelő végén hívja meg a previousHandler.uncaughtException(thread, throwable) függvényt — ez garantálja, hogy a Crashlytics vagy a Sentry megkapja az adatait.
Natív kód esetén jelkezelés szükséges a sigaction()-on keresztül — SIGSEGV, SIGABRT, SIGBUS. Androidon használható a Google Breakpad vagy a Crashpad. iOS-en a 13-as verziótól elérhető a Signals API a mach kivételek kezeléséhez.
Nem — a kezelő telepítése csak a kivétel pillanatában van hatással. Normál alkalmazás működés közben nincs többletterhelés. Az egyetlen kockázat a memóriaszivárgás, ha a kezelő hivatkozást tárol egy Activity-re vagy Context-re, megakadályozva azok szemétgyűjtését.
Összefoglalás
Kulcsrakész mobilalkalmazást fejlesztünk
Az IT Sectr 2017 óta készít iOS és Android alkalmazásokat induló vállalkozásoknak és vállalkozásoknak. Tanácsot adunk, és a legjobb megoldást javasoljuk.
Olvassa el is