Global Exception Handler — механізм централізованого перехоплення необроблених винятків, що запобігає аварійному завершенню мобільного застосунку. За даними Apple Developer, 2024, коректна обробка винятків знижує кількість crash-падінь на 40–60% та покращує користувацький досвід. Без такого обробника будь-який необроблений виняток у фоновому потоці призводить до негайного закриття застосунку.
Головне
Global Exception Handler — це централізований механізм перехоплення винятків, які не були оброблені на рівні окремих функцій або модулів застосунку. У контексті мобільної розробки такий обробник виступає останньою лінією оборони перед аварійним завершенням процесу.
iOS та Android надають вбудовані API для встановлення глобального обробника. Apple використовує NSSetUncaughtExceptionHandler для середовища Objective-C, а Google пропонує Thread.setDefaultUncaughtExceptionHandler у Java/Kotlin. Обидва механізми перехоплюють винятки, які не були перехоплені конструкціями try-catch на всіх потоках застосунку.
За даними Crashlytics (Google, 2024), близько 25% crash відбуваються через необроблені винятки у фонових потоках — область, де Global Exception Handler особливо критичний. Розробники часто фокусуються на UI-потоці, забуваючи про асинхронні операції.
Використання глобального обробника не замінює локальну обробку помилок, а доповнює її. Основне завдання — зберегти максимум інформації про стан застосунку в момент винятку та коректно завершити роботу.
Механізм роботи Global Exception Handler базується на перехопленні сигналів операційної системи або винятків часу виконання. Коли код викидає виняток, який не перехоплений жодним блоком try-catch, управління передається заздалегідь встановленому обробнику.
На платформі iOS обробник реєструється через NSSetUncaughtExceptionHandler та отримує об'єкт NSException з повним стек-трейсом. На Android використовується Thread.setDefaultUncaughtExceptionHandler, який приймає Thread та Throwable — це дає доступ до типу винятку, повідомлення та стеку викликів.
Після отримання даних про crash обробник виконує три обов'язкові дії: запис логу в локальне сховище, надсилання звіту в Crashlytics або Sentry та коректне завершення застосунку. За даними Apple WWDC 2023, час роботи обробника обмежений 5 секундами — після цього система примусово завершує процес.
Для Swift-застосунків починаючи з iOS 13 з'явився Signals API, який обробляє не лише винятки, але й сигнали операційної системи — SIGABRT, SIGSEGV та SIGBUS, розширюючи зону покриття обробника на низькорівневі помилки пам'яті.
Реалізація глобального обробника на iOS вимагає встановлення C-функції через NSSetUncaughtExceptionHandler. Обробник викликається синхронно в момент необробленого винятку та отримує повний контекст помилки.
void handleUncaughtException(NSException exception) {
NSDictionary userInfo = [exception userInfo];
NSArray stackTrace = [exception callStackSymbols];
NSString reason = [exception reason];
// Зберігаємо crash-лог у локальний файл
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);
}
Важлива особливість iOS-реалізації: обробник перехоплює лише Objective-C винятки. Swift-помилки, що використовують механізм throw-catch, не потрапляють у цей обробник — для них потрібна окрема обробка через Swift Error Handling. Починаючи з iOS 14, Apple рекомендує комбінувати NSSetUncaughtExceptionHandler з Signals API для максимального покриття.
За даними Apple Technical Note TN2151, після виклику обробника застосунок має завершитися протягом 5 секунд. Будь-яка спроба продовжити виконання після повернення з обробника призводить до невизначеної поведінки та повторного crash.
Android надає більш гнучкий механізм глобальної обробки винятків через Thread.setDefaultUncaughtExceptionHandler. Обробник отримує посилання на потік, у якому стався виняток, і сам об'єкт Throwable.
class GlobalExceptionHandler : Thread.UncaughtExceptionHandler {
override fun uncaughtException(thread: Thread, throwable: Throwable) {
// Зберігаємо crash-лог у файл
val stackTrace = throwable.stackTraceToString()
val crashLog = "CRASH: ${thread.name}\n${stackTrace}"
val file = File(context.cacheDir, "crash_log.txt")
file.writeText(crashLog)
// Надсилаємо в Crashlytics
FirebaseCrashlytics.getInstance()
.recordException(throwable)
// Завершуємо процес
android.os.Process.killProcess(
android.os.Process.myPid()
)
}
}
// Встановлення в Application.onCreate
class App : Application() {
override fun onCreate() {
super.onCreate()
Thread.setDefaultUncaughtExceptionHandler(
GlobalExceptionHandler()
)
}
}
Ключова відмінність Android-реалізації: кожен потік має власний обробник, і setDefaultUncaughtExceptionHandler встановлює обробник для всіх потоків, для яких не задано індивідуальний. Це забезпечує глобальне покриття — від UI-потоку до фонових AsyncTask та корутин.
На Android 12+ з'явилося обмеження: після виклику uncaughtException застосунок має завершитися протягом 100 мілісекунд. Якщо обробник виконує тривалі операції, система може вбити процес до завершення запису логу. Рекомендується використовувати фоновий сервіс для надсилання crash-звіту.
Перше правило — не намагайтеся відновити роботу застосунку після необробленого винятку. Стан застосунку після crash не визначено, і продовження роботи може призвести до пошкодження даних користувача.
Обмеження за часом — головне технічне обмеження Global Exception Handler. На iOS це 5 секунд, на Android — 100 мілісекунд. Всередині обробника допустимо лише зберегти мінімальний набір даних: тип винятку, стек викликів та стан пари ключових змінних.
Надсилання мережевих запитів, запис у базу даних та складну серіалізацію слід перенести в відкладений механізм — наприклад, зберегти лог у файл та надіслати його при наступному запуску застосунку.
Crash-reporting сервіси — Firebase Crashlytics, Sentry, Bugsnag — самі встановлюють глобальний обробник. Якщо розробник встановлює власний обробник поверх, потрібно передавати управління crash-reporting системі після своїх дій. На Android використовується композиція обробників: виконати свою логіку, потім викликати попередній обробник.
Для Firebase Crashlytics рекомендується взагалі не встановлювати кастомний Thread.setDefaultUncaughtExceptionHandler — Crashlytics SDK робить це автоматично при ініціалізації.
Користувацький контекст — окрім стандартного стеку викликів, корисно логувати версію застосунку, версію ОС, розмір вільної пам'яті та час роботи до crash. Ці дані критично важливі для відтворення та усунення проблеми.
В iOS можна використовувати NSSetUncaughtExceptionHandler не лише для запису, але й для тимчасового зберігання даних у NSUserDefaults з прапорцем synchronize — це гарантує збереження навіть при негайному завершенні процесу.
Обов'язкове тестування — Global Exception Handler має бути протестований на кожному етапі CI/CD. На iOS можна ініціювати тестовий виняток через @throw NSException, на Android — через throw RuntimeException(). Перевіряється, що обробник викликається, лог зберігається та застосунок коректно завершується.
За даними Google I/O 2023, більше 30% crash у продакшені відбуваються на пристроях, які розробник не тестував — різні версії Android, кастомні прошивки, обмежена пам'ять.
Перша і найчастіша помилка — спроба продовжити виконання застосунку після обробки винятку. Після виклику uncaughtException застосунок знаходиться в нестабільному стані, і будь-які подальші операції можуть викликати каскад помилок та пошкодження даних.
Друга помилка — виконання довгих операцій всередині обробника. Мережеві запити, запис великих файлів або складні обчислення не встигають завершитися до примусового завершення процесу. За даними Apple Technical Q&A QA1468, спроба надіслати HTTP-запит всередині обробника — провідна причина втрачених crash-звітів.
Третя помилка — ігнорування фонових потоків. Global Exception Handler, встановлений лише для основного потоку, не захищає від crash у корутинах, DispatchQueue, AsyncTask або RxJava. На Android кожен потік повинен мати свій обробник — і setDefaultUncaughtExceptionHandler вирішує цю проблему лише для потоків без індивідуального обробника.
Четверта помилка — відсутність fallback для сигналів ОС. NSSetUncaughtExceptionHandler на iOS не перехоплює SIGABRT, SIGSEGV та SIGBUS. Для цих сигналів потрібне окреме встановлення обробників через sigaction API. Розробники дізнаються про це лише коли застосунок падає без жодного crash-звіту.
П'ята помилка — логування конфіденційних даних. У crash-лог потрапляють email, токени авторизації або персональні дані користувачів. Це порушує GDPR та Apple App Store Review Guidelines. Завжди фільтруйте дані, що передаються, через regex або whitelist-список дозволених полів.
Часто задавані питання
Ні — після виклику Global Exception Handler стан застосунку не визначено. Будь-яка спроба продовжити роботу може призвести до пошкодження даних. Єдина коректна дія — зберегти crash-лог та завершити процес.
Не всі — на iOS NSSetUncaughtExceptionHandler ловить лише Objective-C винятки. Swift-помилки та сигнали ОС (SIGSEGV, SIGABRT) вимагають окремих обробників. На Android Thread.setDefaultUncaughtExceptionHandler перехоплює всі RuntimeException, але не помилки native-коду через JNI.
Збережіть посилання на попередній обробник через Thread.getDefaultUncaughtExceptionHandler() перед встановленням свого. Наприкінці свого обробника викличте previousHandler.uncaughtException(thread, throwable) — це гарантує, що Crashlytics або Sentry отримають свої дані.
Для native-коду потрібна обробка сигналів через sigaction() — SIGSEGV, SIGABRT, SIGBUS. На Android можна використовувати Google Breakpad або Crashpad. На iOS починаючи з версії 13 з'явився Signals API для обробки mach-винятків.
Ні — встановлення обробника впливає лише на момент виникнення винятку. У нормальній роботі застосунку overhead відсутній. Єдиний ризик — витік пам'яті, якщо обробник зберігає посилання на Activity або Context, запобігаючи їх збиранню сміття.
Підсумки
Ми розробимо мобільний застосунок під ключ
IT Sectr створює застосунки для iOS та Android для стартапів і бізнесу з 2017 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.
Читайте також