← Назад к вопросам

Как перехватить все runtime исключения в приложении

2.0 Middle🔥 162 комментариев
#Архитектура и паттерны#Многопоточность и асинхронность

Комментарии (2)

🐱
deepseek-v3.2PrepBro AI5 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Глобальный перехват Runtime исключений в Android

Для перехвата всех необработанных исключений в Android-приложении используется механизм UncaughtExceptionHandler. Это особенно важно для:

  • Логирования критических ошибок
  • Корректного завершения работы приложения
  • Отправки отчетов об ошибках на сервер
  • Предоставления пользователю понятного сообщения об ошибке

Основной подход через Thread.setDefaultUncaughtExceptionHandler

class MyApplication : Application() {
    
    override fun onCreate() {
        super.onCreate()
        
        val defaultHandler = Thread.getDefaultUncaughtExceptionHandler()
        
        Thread.setDefaultUncaughtExceptionHandler { thread, throwable ->
            // Логирование исключения
            logException(throwable)
            
            // Дополнительные действия
            saveCrashReport(throwable)
            notifyUserAboutCrash()
            
            // Передача оригинальному обработчику для стандартного поведения
            defaultHandler?.uncaughtException(thread, throwable)
        }
    }
    
    private fun logException(throwable: Throwable) {
        // Запись в файл или отправка на сервер
        val logMessage = """
            Time: ${System.currentTimeMillis()}
            Thread: ${Thread.currentThread().name}
            Exception: ${throwable.javaClass.name}
            Message: ${throwable.message}
            StackTrace:
            ${throwable.stackTrace.joinToString("\n")}
        """.trimIndent()
        
        // Пример записи в файл
        File(filesDir, "crash_log.txt").appendText("$logMessage\n\n")
    }
}

Расширенная реализация с сохранением состояния

class GlobalExceptionHandler(
    private val originalHandler: Thread.UncaughtExceptionHandler?
) : Thread.UncaughtExceptionHandler {
    
    override fun uncaughtException(thread: Thread, throwable: Throwable) {
        try {
            // 1. Сбор информации о системе
            val deviceInfo = collectDeviceInfo()
            
            // 2. Сохранение стека вызовов
            val stackTrace = getStackTrace(throwable)
            
            // 3. Сохранение в SharedPreferences для последующей отправки
            saveCrashInfoToPrefs(deviceInfo, stackTrace)
            
            // 4. Можно попытаться сохранить текущее состояние приложения
            saveApplicationState()
            
        } finally {
            // Всегда вызываем оригинальный обработчик
            originalHandler?.uncaughtException(thread, throwable)
        }
    }
    
    private fun collectDeviceInfo(): Map<String, String> {
        return mapOf(
            "BRAND" to Build.BRAND,
            "MODEL" to Build.MODEL,
            "SDK_INT" to Build.VERSION.SDK_INT.toString(),
            "APP_VERSION" to BuildConfig.VERSION_NAME,
            "TIME" to SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault())
                .format(Date())
        )
    }
}

Важные аспекты реализации

Ключевые моменты, которые необходимо учитывать:

  • Порядок обработки: Всегда сохраняйте ссылку на оригинальный обработчик и вызывайте его в конце
  • Время выполнения: Обработчик должен работать быстро, так как приложение находится в нестабильном состоянии
  • Потокобезопасность: Убедитесь, что ваша реализация потокобезопасна
  • Взаимодействие с UI: Не пытайтесь показывать диалоги или Toast в обработчике

Пример регистрации в разных точках приложения

// Регистрация в Application классе (рекомендуется)
class App : Application() {
    companion object {
        lateinit var instance: App
    }
    
    override fun onCreate() {
        super.onCreate()
        instance = this
        setupExceptionHandler()
    }
}

// Альтернативная регистрация в Activity
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        
        if (savedInstanceState == null) {
            setupActivityExceptionHandler()
        }
    }
    
    private fun setupActivityExceptionHandler() {
        val currentHandler = Thread.getDefaultUncaughtExceptionHandler()
        Thread.setDefaultUncaughtExceptionHandler { thread, throwable ->
            // Специфичная для Activity обработка
            if (thread.name.contains("main")) {
                saveCurrentActivityState()
            }
            currentHandler?.uncaughtException(thread, throwable)
        }
    }
}

Ограничения и предупреждения

Важные ограничения:

  • Не все исключения можно перехватить: Некоторые системные ошибки (Native crashes, ANR) не проходят через этот механизм
  • Производительность: Слишком сложная логика в обработчике может ухудшить процесс завершения приложения
  • Совместимость: Разные версии Android могут иметь особенности в обработке исключений
  • Повторные исключения: Обработчик сам может выбросить исключение, что приведет к немедленному краху

Альтернативные подходы

  • Firebase Crashlytics: Автоматический сбор и анализ крэшей
  • ACRA: Библиотека для отправки отчетов об ошибках
  • Custom solutions: Собственные системы мониторинга ошибок

Рекомендация: Для продакшн-приложений лучше использовать комбинацию стандартного обработчика для базовой логики и специализированных сервисов (Crashlytics, Sentry) для полноценного мониторинга ошибок.

Как перехватить все runtime исключения в приложении | PrepBro