- Error handling on Android part 1: how exceptions work for JVM and Android apps
- Error handling on Android
- How do exceptions work for JVM and Android apps?
- What is an Exception object?
- Anatomy of the Throwable class
- Exception handling hierarchy
- Would you like to know more?
- Исключения
- Несколько исключений
- Вложенные операторы try
- Оператор throw
- Оператор throws
- Оператор finally
- Встроенные исключения Java
- Создание собственных классов исключений
- Перехват произвольных исключений
- Основные правила обработки исключений
Error handling on Android part 1: how exceptions work for JVM and Android apps
Error handling on Android
How many times have you been in the middle of using a new shiny app, only to have it crash on you?
This is the first in a series of posts that will investigate how the exception handling mechanism works in Java and Android, and how crash reporting SDKs can capture diagnostic information, so that you’re not flying blind in production.
How do exceptions work for JVM and Android apps?
Exceptions should be thrown in exceptional circumstances where the calling code needs to decide how to recover from an error condition.
What is an Exception object?
A [CODE]Throwable[/CODE] is a special type of object which can be thrown and alter the execution path of a JVM application. For example, the code snippet below throws an [CODE]IllegalStateException[/CODE]:
— CODE language-kotlin —
fun main() <
В В В try <
В В В В В В В throw IllegalStateException()
В В В В В В В println(«Hello World»)
В В В > catch (exc: Throwable) <
В В В В В В В println(«Something went wrong»)
В В В >
>
Throwing an exception means that the execution flow changes, and ‘Hello World’ is never printed. Instead, the program counter will jump to the nearest catch block, and executes the error recovery code within, meaning our program prints вЂSomething went wrong’ instead.
Of course, it doesn’t always make sense to recover from a failure — for example, if an [CODE]OutOfMemoryError[/CODE] is thrown by the JVM, there is very little prospect of ever recovering from this condition. In this case it makes sense to leave the [CODE]Throwable as unhandled[/CODE], and allow the process to terminate so the user can restart the app from a fresh state.
Anatomy of the Throwable class
[CODE]Throwable[/CODE] has two direct subclasses: [CODE]Exception[/CODE], and [CODE]Error[/CODE]. Typically an [CODE]Error[/CODE] is thrown in conditions where recovery is not possible, and an [CODE]Exception[/CODE] where recovery is possible. Additionally, there are many subclasses of [CODE]Exception[/CODE] which convey additional meaning — for example, an [CODE]IllegalArgumentException[/CODE] would indicate the programmer passed an invalid argument, and an [CODE]IllegalStateException[/CODE] would indicate that the program encountered an unanticipated state.
— CODE language-kotlin —
fun main() <
В В В try <
В В В В В В В throw IllegalStateException(«This should never happen!»)
В В В В В В В println(«Hello World»)
В В В > catch (exc: Throwable) <
В В В В В В В println(«Something went wrong»)
В В В >
>
Let’s consider the above snippet again. The constructed [CODE]IllegalStateException[/CODE] object captures a snapshot of the application at the time of the error condition:
— CODE language-kotlin —
java.lang.IllegalStateException: This should never happen!
at com.example.myapplication.Exceptions101Kt.foo(Exceptions101.kt:12)
at com.example.myapplication.Exceptions101Kt.main(Exceptions101.kt:5)
at com.example.myapplication.Exceptions101Kt.main(Exceptions101.kt)
This is commonly called a stacktrace. Each line represents a single frame in the application’s call stack at the time of the error, which match the filename, method name, and line number of our original code snippet.
A stacktrace can also contain other useful information, such as program state, which in this case is a static error message, but we could equally pass in arbitrary variables.
Exception handling hierarchy
After throwing an exception, an exception handler must be found to handle the exception, or the app will terminate. In the JVM, this is a well-defined hierarchy, which we’ll run through here.
First up in the exception handling hierarchy is a catch block:
— CODE language-kotlin —
try <
В В В crashyCode()
> catch (exc: IllegalStateException) <
В В В // handle throwables of type IllegalStateException
>
If a catch block isn’t available in the current stack frame, but is defined further down the call stack, then the exception will be handled there.
Next in the hierarchy is implementations of [CODE]UncaughtExceptionHandler[/CODE]. This interface contains a single method which is invoked whenever a [CODE]Throwable[/CODE] is thrown, after the handler has been set:
— CODE language-kotlin —
val currentThread = Thread.currentThread()
currentThread.setUncaughtExceptionHandler < thread, exc ->
В В В // handle all uncaught JVM exceptions in the current Thread
>
It’s possible to set an [CODE]UncaughtExceptionHandler[/CODE] in a few different places; the JVM has a defined hierarchy for these. First, if a handler has been set on the current [CODE]Thread[/CODE], this will be invoked. Next up will be a handler on the [CODE]ThreadGroup[/CODE], before finally, the default handler is invoked, which will handle all uncaught JVM exceptions by printing a stacktrace, and then terminating the app.
— CODE language-kotlin —
Thread.setDefaultUncaughtExceptionHandler < thread, exc ->
В В В // handle all uncaught JVM exceptions
>
It’s the default [CODE]UncaughtExceptionHandler[/CODE] that is most interesting from an error reporting point-of-view, and it’s the default [CODE]UncaughtExceptionHandler[/CODE] that is responsible for showing that all too familiar crash dialog on Android.
The [CODE]UncaughtExceptionHandler[/CODE] interface is the building block of all crash reporting SDKs on the JVM, such as bugsnag-android or bugsnag-java. Read on in part two to learn how we can define a custom handler for uncaught exceptions, and use it to create a crash reporting SDK.
Would you like to know more?
Hopefully this has helped you learn a bit more about Error Handling on Android. If you have any questions or feedback, please feel free to get in touch.
Источник
Исключения
Исключение — это нештатная ситуация, ошибка во время выполнения программы. Самый простой пример — деление на ноль. Можно вручную отслеживать возникновение подобных ошибок, а можно воспользоваться специальным механизмом исключений, который упрощает создание больших надёжных программ, уменьшает объём необходимого кода и повышает уверенность в том, что в приложении не будет необработанной ошибки.
В методе, в котором происходит ошибка, создаётся и передаётся специальный объект. Метод может либо обработать исключение самостоятельно, либо пропустить его. В любом случае исключение ловится и обрабатывается. Исключение может появиться благодаря самой системе, либо вы сами можете создать его вручную. Системные исключения возникают при неправильном использовании языка Java или запрещённых приёмов доступа к системе. Ваши собственные исключения обрабатывают специфические ошибки вашей программы.
Вернёмся к примеру с делением. Деление на нуль может предотвратить проверкой соответствующего условия. Но что делать, если знаменатель оказался нулём? Возможно, в контексте вашей задачи известно, как следует поступить в такой ситуации. Но, если нулевой знаменатель возник неожиданно, деление в принципе невозможно, и тогда необходимо возбудить исключение, а не продолжать исполнение программы.
Существует пять ключевых слов, используемых в исключениях: try, catch, throw, throws, finally. Порядок обработки исключений следующий.
Операторы программы, которые вы хотите отслеживать, помещаются в блок try. Если исключение произошло, то оно создаётся и передаётся дальше. Ваш код может перехватить исключение при помощи блока catch и обработать его. Системные исключения автоматически передаются самой системой. Чтобы передать исключение вручную, используется throw. Любое исключение, созданное и передаваемое внутри метода, должно быть указано в его интерфейсе ключевым словом throws. Любой код, который следует выполнить обязательно после завершения блока try, помещается в блок finally
Схематически код выглядит так:
Существует специальный класс для исключений Trowable. В него входят два класса Exception и Error.
Класс Exception используется для обработки исключений вашей программой. Вы можете наследоваться от него для создания собственных типов исключений. Для распространённых ошибок уже существует класс RuntimeException, который может обрабатывать деление на ноль или определять ошибочную индексацию массива.
Класс Error служит для обработки ошибок в самом языке Java и на практике вам не придётся иметь с ним дело.
Прежде чем научиться обрабатывать исключения, нам (как и нормальному любопытному коту) хочется посмотреть, а что происходит, если ошибку не обработать. Давайте разделим число котов в вашей квартире на ноль, хотя мы и знаем, что котов на ноль делить нельзя!
Я поместил код в обработчик щелчка кнопки. Когда система времени выполнения Java обнаруживает попытку деления на ноль, она создаёт объект исключения и передаёт его. Да вот незадача, никто не перехватывает его, хотя это должны были сделать вы. Видя вашу бездеятельность, объект перехватывает стандартный системный обработчик Java, который отличается вредных характером. Он останавливает вашу программу и выводит сообщение об ошибке, которое можно увидеть в журнале LogCat:
Как видно, созданный объект исключения принадлежит к классу ArithmeticException, далее системный обработчик любезно вывел краткое описание ошибки и место возникновения.
Вряд ли пользователи вашей программы будут довольны, если вы так и оставите обработку ошибки системе. Если программа будет завершаться с такой ошибкой, то скорее всего вашу программу просто удалят. Посмотрим, как мы можем исправить ситуацию.
Поместим проблемный код в блок try, а в блоке catch обработаем исключение.
Теперь программа аварийно не закрывается, так как мы обрабатываем ситуацию с делением на ноль.
В данном случае мы уже знали, к какому классу принадлежит получаемая ошибка, поэтому в блоке catch сразу указали конкретный тип. Обратите внимание, что последний оператор в блоке try не срабатывает, так как ошибка происходит раньше строчкой выше. Далее выполнение передаётся в блок catch, далее выполняются следующие операторы в обычном порядке.
Операторы try и catch работают совместно в паре. Хотя возможны ситуации, когда catch может обрабатывать несколько вложенных операторов try.
Если вы хотите увидеть описание ошибки, то параметр e и поможет увидеть ёго.
По умолчанию, класс Trowable, к которому относится ArithmeticException возвращает строку, содержащую описание исключения. Но вы можете и явно указать метод e.toString.
Несколько исключений
Фрагмент кода может содержать несколько проблемных мест. Например, кроме деления на ноль, возможна ошибка индексации массива. В таком случае вам нужно создать два или более операторов catch для каждого типа исключения. Причём они проверяются по порядку. Если исключение будет обнаружено у первого блока обработки, то он будет выполнен, а остальные проверки пропускаются и выполнение программы продолжается с места, который следует за блоком try/catch.
В примере мы добавили массив с тремя элементами, но обращаемся к четвёртому элементу, так как забыли, что отсчёт у массива начинается с нуля. Если оставить значение переменной zero равным нулю, то сработает обработка первого исключения деления на ноль, и мы даже не узнаем о существовании второй ошибки. Но допустим, что в результате каких-то вычислений значение переменной стало равно единице. Тогда наше исключение ArithmeticException не сработает. Но сработает новое добавленное исключение ArrayIndexOutOfBoundsException. А дальше всё пойдёт как раньше.
Тут всегда нужно помнить одну особенность. При использовании множественных операторов catch обработчики подклассов исключений должные находиться выше, чем обработчики их суперклассов. Иначе, суперкласс будет перехватывать все исключения, имея большую область перехвата. Иными словами, Exception не должен находиться выше ArithmeticException и ArrayIndexOutOfBoundsException. К счастью, среда разработки сама замечает непорядок и предупреждает вас, что такой порядок не годится. Увидев такую ошибку, попробуйте перенести блок обработки исключений ниже.
Вложенные операторы try
Операторы try могут быть вложенными. Если вложенный оператор try не имеет своего обработчика catch для определения исключения, то идёт поиск обработчика catch у внешнего блока try и т.д. Если подходящий catch не будет найден, то исключение обработает сама система (что никуда не годится).
Оператор throw
Часть исключений может обрабатывать сама система. Но можно создать собственные исключения при помощи оператора throw. Код выглядит так:
Вам нужно создать экземпляр класса Throwable или его наследников. Получить объект класса Throwable можно в операторе catch или стандартным способом через оператор new.
Мы могли бы написать такой код для кнопки:
Мы объявили объект класса Cat, но забыли его проинициализировать, например, в onCreate(). Теперь нажатие кнопки вызовет исключение, которое обработает система, а в логах мы можем прочитать сообщение об ошибке. Возможно, вы захотите использовать другое исключение, например, throw new UnsupportedOperationException(«Котик не инициализирован»);.
В любом случае мы передали обработку ошибки системе. В реальном приложении вам нужно обработать ошибку самостоятельно.
Поток выполнения останавливается непосредственно после оператора throw и другие операторы не выполняются. При этом ищется ближайший блок try/catch соответствующего исключению типа.
Перепишем пример с обработкой ошибки.
Мы создали новый объект класса NullPointerException. Многие классы исключений кроме стандартного конструктора по умолчанию с пустыми скобками имеют второй конструктор с строковым параметром, в котором можно разместить подходящую информацию об исключении. Получить текст из него можно через метод getMessage(), что мы и сделали в блоке catch.
Теперь программа не закроется аварийно, а будет просто выводить сообщения в всплывающих Toast.
Оператор throws
Если метод может породить исключение, которое он сам не обрабатывает, он должен задать это поведение так, чтобы вызывающий его код мог позаботиться об этом исключении. Для этого к объявлению метода добавляется конструкция throws, которая перечисляет типы исключений (кроме исключений Error и RuntimeException и их подклассов).
Общая форма объявления метода с оператором throws:
В фрагменте список_исключений можно указать список исключений через запятую.
Создадим метод, который может породить исключение, но не обрабатывает его. А в щелчке кнопки вызовем его.
Если вы запустите пример, то получите ошибку. Исправим код.
Мы поместили вызов метода в блок try и вызвали блок catch с нужным типом исключения. Теперь ошибки не будет.
Оператор finally
Когда исключение передано, выполнение метода направляется по нелинейному пути. Это может стать источником проблем. Например, при входе метод открывает файл и закрывает при выходе. Чтобы закрытие файла не было пропущено из-за обработки исключения, был предложен механизм finally.
Ключевое слово finally создаёт блок кода, который будет выполнен после завершения блока try/catch, но перед кодом, следующим за ним. Блок будет выполнен, независимо от того, передано исключение или нет. Оператор finally не обязателен, однако каждый оператор try требует наличия либо catch, либо finally.
Встроенные исключения Java
Существуют несколько готовых системных исключений. Большинство из них являются подклассами типа RuntimeException и их не нужно включать в список throws. Вот небольшой список непроверяемых исключений.
- ArithmeticException — арифметическая ошибка, например, деление на нуль
- ArrayIndexOutOfBoundsException — выход индекса за границу массива
- ArrayStoreException — присваивание элементу массива объекта несовместимого типа
- ClassCastException — неверное приведение
- EnumConstantNotPresentException — попытка использования неопределённого значения перечисления
- IllegalArgumentException — неверный аргумент при вызове метода
- IllegalMonitorStateException — неверная операция мониторинга
- IllegalStateException — некорректное состояние приложения
- IllegalThreadStateException — запрашиваемая операция несовместима с текущим потоком
- IndexOutofBoundsException — тип индекса вышел за допустимые пределы
- NegativeArraySizeException — создан массив отрицательного размера
- NullPointerException — неверное использование пустой ссылки
- NumberFormatException — неверное преобразование строки в числовой формат
- SecurityException — попытка нарушения безопасности
- StringIndexOutOfBounds — попытка использования индекса за пределами строки
- TypeNotPresentException — тип не найден
- UnsupportedOperationException — обнаружена неподдерживаемая операция
Список проверяемых системных исключений, которые можно включать в список throws.
- ClassNotFoundException — класс не найден
- CloneNotSupportedException — попытка клонировать объект, который не реализует интерфейс Cloneable
- IllegalAccessException — запрещен доступ к классу
- InstantiationException — попытка создать объект абстрактного класса или интерфейса
- InterruptedException — поток прерван другим потоком
- NoSuchFieldException — запрашиваемое поле не существует
- NoSuchMethodException — запрашиваемый метод не существует
- ReflectiveOperationException — исключение, связанное с рефлексией
Создание собственных классов исключений
Система не может предусмотреть все исключения, иногда вам придётся создать собственный тип исключения для вашего приложения. Вам нужно наследоваться от Exception (напомню, что этот класс наследуется от Trowable) и переопределить нужные методы класса Throwable. Либо вы можете наследоваться от уже существующего типа, который наиболее близок по логике с вашим исключением.
- final void addSuppressed(Throwable exception) — добавляет исключение в список подавляемых исключений (JDK 7)
- Throwable fillInStackTrace() — возвращает объект класса Throwable, содержащий полную трассировку стека.
- Throwable getCause() — возвращает исключение, лежащее под текущим исключение или null
- String getLocalizedMessage() — возвращает локализованное описание исключения
- String getMessage() — возвращает описание исключения
- StackTraceElement[] getStackTrace() — возвращает массив, содержащий трассировку стека и состояний из элементов класса StackTraceElement
- final Throwable[] getSuppressed() — получает подавленные исключения (JDK 7)
- Throwable initCause(Throwable exception) — ассоциирует исключение с вызывающим исключением. Возвращает ссылку на исключение.
- void printStackTrace() — отображает трассировку стека
- void printStackTrace(PrintStream stream) — посылает трассировку стека в заданный поток
- void printStackTrace(PrintWriter stream) — посылает трассировку стека в заданный поток
- void setStackTrace(StackTraceElement elements[]) — устанавливает трассировку стека для элементов (для специализированных приложений)
- String toString() — возвращает объект класса String, содержащий описание исключения.
Самый простой способ — создать класс с конструктором по умолчанию.
Мы создали собственный класс HungryCatException, в методе testMethod() его возбуждаем, а по нажатию кнопки вызываем этот метод. В результате наше исключение сработает.
Создать класс исключения с конструктором, который получает аргумент-строку, также просто.
Ещё вариант. Добавим также метод toString().
Теперь класс содержит два конструктора. Во втором конструкторе используется конструктор родительского класса с аргументом String, вызываемый ключевым словом super.
Перехват произвольных исключений
Можно создать универсальный обработчик, перехватывающий любые типы исключения. Осуществляется это перехватом базового класса всех исключений Exception:
Подобная конструкция не упустит ни одного исключения, поэтому её следует размещать в самом конце списка обработчиков, во избежание блокировки следующих за ней обработчиков исключений.
Основные правила обработки исключений
Используйте исключения для того, чтобы:
- обработать ошибку на текущем уровне (избегайте перехватывать исключения, если не знаете, как с ними поступить)
- исправить проблему и снова вызвать метод, возбудивший исключение
- предпринять все необходимые действия и продолжить выполнение без повторного вызова действия
- попытаться найти альтернативный результат вместо того, который должен был бы произвести вызванный метод
- сделать все возможное в текущем контексте и заново возбудить это же исключение, перенаправив его на более высокий уровень
- сделать все, что можно в текущем контексте, и возбудить новое исключение, перенаправив его на более высокий уровень
- завершить работу программы
- упростить программу (если используемая схема обработки исключений делает все только сложнее, значит, она никуда не годится)
- добавить вашей библиотеке и программе безопасности
Источник