Global Exception Handler: същност, принцип на работа и реализация в проекти

Автор: IT Sectr Публикувано: 2026-05-27 Време за четене: 8 мин

Global Exception Handler — механизъм за централизирано прихващане на необработени изключения, предотвратяващ аварийното затваряне на мобилно приложение. Според данни на Apple Developer, 2024, правилната обработка на изключения намалява броя на crash-овете с 40–60% и подобрява потребителското изживяване. Без такъв манипулатор всяко необработено изключение във фонов низ води до незабавно затваряне на приложението.

Основни точки

  • Global Exception Handler — точка за събиране на всички необработени изключения в приложението, предотвратяваща crash
  • iOS NSSetUncaughtExceptionHandler — C функция за прихващане на Objective-C изключения на платформата Apple
  • Android Thread.setDefaultUncaughtExceptionHandler — вграден механизъм на платформата за глобално прихващане на изключения
  • Логване преди затваряне — основна задача на манипулатора: запазване на информация за crash преди приключване на процеса
  • Graceful degradation — манипулаторът позволява показване на правилен екран за грешка вместо аварийно затваряне

Какво е Global Exception Handler?

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, разширявайки обхвата на манипулатора до нискоривневи грешки в паметта.

Реализация на Global Exception Handler в iOS

Реализация — глобалният манипулатор в iOS изисква настройка на C функция чрез NSSetUncaughtExceptionHandler. Манипулаторът се извиква синхронно в момента на необработено изключение и получава пълния контекст на грешката.

objective-c
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.

Реализация на Global Exception Handler в Android

Android предоставя по-гъвкав механизъм за глобална обработка на изключения чрез Thread.setDefaultUncaughtExceptionHandler. Манипулаторът получава референция към нишката, в която е възникнало изключението, и самия обект Throwable.

kotlin
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 отчета.

Най-добри практики при работа с Global Exception Handler

Първо правило — не се опитвайте да възстановите приложението след необработено изключение. Състоянието на приложението след crash е неопределено и продължаването на работа може да доведе до повреда на потребителски данни.

Минимизиране на времето за работа на манипулатора

Времево ограничение — основното техническо ограничение на Global Exception Handler. В iOS е 5 секунди, в Android — 100 милисекунди. Вътре в манипулатора е позволено само запазване на минимален набор от данни: тип на изключението, стек на извикванията и състояние на няколко ключови променливи.

Изпращането на мрежови заявки, запис в база данни и сложна сериализация трябва да се прехвърлят към отложен механизъм — например, запазване на лог във файл и изпращането му при следващото стартиране на приложението.

Комбиниране със системи за отчитане на crash

Услуги за отчитане на crash — Firebase Crashlytics, Sentry, Bugsnag — сами инсталират глобален манипулатор. Ако разработчикът инсталира свой собствен манипулатор върху тях, той трябва да предаде управлението на системата за отчитане на crash след своите действия. В 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 решава този проблем само за нишки без индивидуален манипулатор.

Четвърта грешка — липса на резервен вариант за сигнали на операционната система. NSSetUncaughtExceptionHandler в iOS не прихваща SIGABRT, SIGSEGV и SIGBUS. За тези сигнали е необходимо отделно задаване на манипулатори чрез sigaction API. Разработчиците разбират за това едва когато приложението падне без нито един crash отчет.

Пета грешка — логване на поверителни данни. В crash лога попадат имейли, токени за удостоверяване или лични данни на потребителите. Това нарушава GDPR и Apple App Store Review Guidelines. Винаги филтрирайте предаваните данни чрез regex или белия списък на разрешените полета.

Често задавани въпроси

Може ли да се възстанови работата на приложението след глобално изключение?

Не — след извикване на Global Exception Handler състоянието на приложението е неопределено. Всеки опит за продължаване на работа може да доведе до повреда на данни. Единственото правилно действие е да запазите crash лога и да прекратите процеса.

Прихваща ли Global Exception Handler всички типове грешки?

Не всички — в iOS NSSetUncaughtExceptionHandler хваща само Objective-C изключения. Swift грешките и сигналите на ОС (SIGSEGV, SIGABRT) изискват отделни манипулатори. В Android Thread.setDefaultUncaughtExceptionHandler прихваща всички RuntimeException, но не и грешки на native код чрез JNI.

Как да предам управлението на системата за отчитане на crash след своя манипулатор?

Запазете референция към предишния манипулатор чрез Thread.getDefaultUncaughtExceptionHandler() преди да зададете своя манипулатор. В края на своя манипулатор извикайте previousHandler.uncaughtException(thread, throwable) — това гарантира, че Crashlytics или Sentry ще получат своите данни.

Какво да направя, ако crash е възникнал в C/C++ native код?

За native код се изисква обработка на сигнали чрез sigaction() — SIGSEGV, SIGABRT, SIGBUS. В Android може да се използва Google Breakpad или Crashpad. В iOS от версия 13 насам е наличен Signals API за обработка на mach изключения.

Може ли Global Exception Handler да повлияе на производителността?

Не — задаването на манипулатора влияе само в момента на възникване на изключението. При нормална работа на приложението няма допълнително натоварване. Единственият риск е изтичане на памет, ако манипулаторът съхранява референция към Activity или Context, предотвратявайки събирането на отпадъци.

Резюме

  • Global Exception Handler — последна линия на защита преди crash, задължителен във всяко продукционно приложение
  • iOS NSSetUncaughtExceptionHandler прихваща Objective-C изключения с ограничение от 5 секунди за обработка
  • Android Thread.setDefaultUncaughtExceptionHandler работи за всички нишки без персонален манипулатор
  • Времето за работа на манипулатора трябва да е минимално — запазване на данни и прекратяване на процеса без опит за възстановяване
  • Сигнали на ОС (SIGSEGV, SIGABRT) не се прихващат от стандартните манипулатори — изисква се sigaction API
  • Системите за отчитане на crash трябва да се извикват чрез композиция на манипулатори, предавайки управлението след собствената логика
  • Тестване на манипулатора в CI/CD — задължителен етап, предотвратяващ загубата на crash отчети в продукция

Ще разработим мобилно приложение под ключ

IT Sectr създава iOS и Android приложения за стартъпи и бизнеси от 2017 г. Ще ви консултираме и ще предложим най-доброто решение.

Обсъдете проекта

Прочетете също