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 года. Мы проконсультируем вас и предложим наилучшее решение.
Читайте также