Приложение для Android аварийно завершает работу при неожиданном завершении, вызванном необработанным исключением или сигналом. Приложение, написанное на Java или Kotlin, аварийно завершает работу, если оно генерирует необработанное исключение, представленное классом Throwable . Приложение, написанное на машинном коде или C++, аварийно завершает работу, если во время его выполнения возникает необработанный сигнал, например SIGSEGV .
При сбое приложения Android завершает процесс приложения и отображает диалоговое окно, сообщающее пользователю о том, что приложение остановлено, как показано на рисунке 1.

Для сбоя приложения не обязательно, чтобы оно работало в фоновом режиме. Любой компонент приложения, даже такие компоненты, как широковещательные приемники или поставщики контента, работающие в фоновом режиме, могут привести к сбою приложения. Эти сбои часто сбивают пользователей с толку, поскольку они не взаимодействовали активно с вашим приложением.
Если ваше приложение вылетает, вы можете воспользоваться инструкциями на этой странице, чтобы диагностировать и устранить проблему.
Проведите диагностику сбоев.
После того, как вы определили, что ваше приложение сообщает о сбоях, следующим шагом будет их диагностика. Устранение сбоев может быть сложной задачей. Однако, если вы сможете определить первопричину сбоя, скорее всего, вы сможете найти решение.
Существует множество ситуаций, которые могут привести к сбою в вашем приложении. Некоторые причины очевидны, например, проверка на нулевое значение или пустую строку, но другие более сложны, например, передача недопустимых аргументов в API или даже сложные многопоточные взаимодействия.
При сбоях в Android создается трассировка стека, которая представляет собой снимок последовательности вложенных функций, вызываемых в вашей программе до момента сбоя. Вы можете просмотреть трассировку стека при сбоях в Android Vitals .
Как читать трассировку стека
The first step to fix a crash is to identify the place where it happens. You can use the stack trace available in the report details if you are using Play Console or the output of the logcat tool. If you don't have a stack trace available, you should locally reproduce the crash, either by manually testing the app or by reaching out to affected users, and reproduce it while using logcat.
Приведенный ниже пример трассировки показывает сбой в приложении, написанном с использованием Jetpack Compose:
--------- beginning of crash
AndroidRuntime: FATAL EXCEPTION: main
Process: com.android.developer.crashsample, PID: 3686
java.lang.NullPointerException
at com.android.developer.crashsample.ComposableSingletons$MainActivityKt.lambda$0(MainActivity.kt:27)
at androidx.compose.foundation.ClickableNode.handleUpEvent(Clickable.kt:958)
at androidx.compose.foundation.ClickableNode.onPointerEvent-H0pRuoY(Clickable.kt:895)
at androidx.compose.ui.input.pointer.Node.dispatchMainEventPass(HitPathTracker.kt:446)
at androidx.compose.ui.input.pointer.HitPathTracker.dispatchChanges(HitPathTracker.kt:181)
at androidx.compose.ui.input.pointer.PointerInputEventProcessor.process-BIzXfog(PointerInputEventProcessor.kt:118)
at androidx.compose.ui.platform.AndroidComposeView.dispatchTouchEvent(AndroidComposeView.android.kt:2650)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2969)
at android.app.Activity.dispatchTouchEvent(Activity.java:4683)
at android.os.Looper.loop(Looper.java:398)
at android.app.ActivityThread.main(ActivityThread.java:9569)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:918)
Трассировка стека отображает два важных для отладки сбоя фрагмента информации:
- Тип выброшенного исключения.
- Участок кода, где возникает исключение.
Тип выброшенного исключения обычно очень точно указывает на причину проблемы. Проверьте, является ли это IOException , OutOfMemoryError или что-то другое, и найдите документацию по классу исключения.
На второй строке трассировки стека указаны класс, метод, файл и номер строки исходного файла, в котором возникло исключение. Для каждой вызванной функции отдельная строка показывает место предыдущего вызова (так называемый кадр стека).
By walking up the stack and examining the code, you may find a place that is passing an incorrect value. If your code doesn't appear in the stack trace, it is likely that somewhere, you passed an invalid parameter into an asynchronous operation. You can often figure out what happened by examining each line of the stack trace, finding any API classes that you used, and confirming that the parameters you passed were correct, and that you called it from a place that is allowed.
Трассировка стека для приложений, использующих код на C и C++, работает практически одинаково.
*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
Build fingerprint: 'google/foo/bar:10/123.456/78910:user/release-keys'
ABI: 'arm64'
Timestamp: 2020-02-16 11:16:31+0100
pid: 8288, tid: 8288, name: com.example.testapp >>> com.example.testapp <<<
uid: 1010332
signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x0
Cause: null pointer dereference
x0 0000007da81396c0 x1 0000007fc91522d4 x2 0000000000000001 x3 000000000000206e
x4 0000007da8087000 x5 0000007fc9152310 x6 0000007d209c6c68 x7 0000007da8087000
x8 0000000000000000 x9 0000007cba01b660 x10 0000000000430000 x11 0000007d80000000
x12 0000000000000060 x13 0000000023fafc10 x14 0000000000000006 x15 ffffffffffffffff
x16 0000007cba01b618 x17 0000007da44c88c0 x18 0000007da943c000 x19 0000007da8087000
x20 0000000000000000 x21 0000007da8087000 x22 0000007fc9152540 x23 0000007d17982d6b
x24 0000000000000004 x25 0000007da823c020 x26 0000007da80870b0 x27 0000000000000001
x28 0000007fc91522d0 x29 0000007fc91522a0
sp 0000007fc9152290 lr 0000007d22d4e354 pc 0000007cba01b640
backtrace:
#00 pc 0000000000042f89 /data/app/com.example.testapp/lib/arm64/libexample.so (com::example::Crasher::crash() const)
#01 pc 0000000000000640 /data/app/com.example.testapp/lib/arm64/libexample.so (com::example::runCrashThread())
#02 pc 0000000000065a3b /system/lib/libc.so (__pthread_start(void*))
#03 pc 000000000001e4fd /system/lib/libc.so (__start_thread)
Если в трассировках стека нативных приложений вы не видите информации на уровне классов и функций, вам может потребоваться сгенерировать файл отладочных символов нативного приложения и загрузить его в консоль Google Play. Дополнительную информацию см. в разделе «Деобфускация трассировок стека сбоев». Общую информацию о сбоях нативных приложений см. в разделе «Диагностика сбоев нативных приложений» .
Советы по воспроизведению сбоя
It's possible that you can't quite reproduce the problem just by starting an emulator or connecting your device to your computer. Development environments tend to have more resources, such as bandwidth, memory, and storage. Use the type of exception to determine what could be the resource that is scarce, or find a correlation between the version of Android, device type or your app's version.
Ошибки памяти
Если возникает ошибка OutOfMemoryError , вы можете создать эмулятор с небольшим объемом памяти для тестирования. На рисунке 2 показаны настройки диспетчера AVD, где вы можете управлять объемом памяти на устройстве.

Сетевые исключения
Поскольку пользователи часто перемещаются между зонами покрытия мобильной или Wi-Fi сети, в приложении сетевые исключения обычно не следует рассматривать как ошибки , а скорее как нормальные условия работы, возникающие неожиданно.
Если вам необходимо воспроизвести сетевое исключение, например UnknownHostException , попробуйте включить режим полета, пока ваше приложение пытается использовать сеть.
Другой вариант — снизить качество сети в эмуляторе, выбрав эмуляцию скорости сети, задержки сети или и то, и другое. Вы можете использовать настройки скорости и задержки в диспетчере AVD или запустить эмулятор с флагами -netdelay и -netspeed , как показано в следующем примере командной строки:
emulator -avd [your-avd-image] -netdelay 20000 -netspeed gsm
В этом примере задается задержка в 20 секунд для всех сетевых запросов и скорость загрузки и выгрузки 14,4 Кбит/с. Для получения дополнительной информации о параметрах командной строки эмулятора см. раздел «Запуск эмулятора из командной строки» .
Чтение с помощью logcat
Как только вам удастся воспроизвести сбой, вы можете использовать такой инструмент, как logcat , чтобы получить более подробную информацию.
Вывод команды logcat покажет вам, какие ещё сообщения журнала вывели, а также другие сообщения из системы. Не забудьте отключить все лишние сообщения Log , которые вы добавили, поскольку их вывод расходует ресурсы процессора и заряд батареи во время работы вашего приложения.
Предотвращение сбоев, вызванных исключениями нулевого указателя.
Null pointer exceptions (identified by the runtime error type NullPointerException ) occur when you're trying to access an object that is null, typically by invoking its methods or accessing its members. Null pointer exceptions are the largest cause of app crashes on Google Play. The purpose of null is to signify that the object is missing - for example, it hasn't been created or assigned yet.
Чтобы избежать исключений NullPointerException, необходимо убедиться, что ссылки на объекты, с которыми вы работаете, не равны нулю, прежде чем вызывать методы или пытаться получить доступ к их членам. Если ссылка на объект равна нулю, обработайте этот случай должным образом (например, выйдите из метода до выполнения каких-либо операций с ссылкой на объект и запишите информацию в отладочный журнал).
Поскольку вам не нужны проверки на null для каждого параметра каждого вызываемого метода, вы можете полагаться на IDE или на тип объекта, который будет указывать на возможность присвоения значения null.
Котлин
В Kotlin возможность присвоения значения null является частью системы типов. Например, переменная должна быть объявлена с самого начала как допускающая или не допускающая значение null. Допускающие значение null типы помечаются знаком ? :
// non-null
var s: String = "Hello"
// null
var s: String? = "Hello"
Непустые переменные нельзя присвоить значение NULL, а пустые переменные необходимо проверять на возможность присвоения значения NULL, прежде чем использовать их в качестве пустых.
Если вы не хотите явно проверять наличие значения null, вы можете использовать оператор безопасного вызова ?.
val length: Int? = string?.length // length is a nullable int
// if string is null, then length is null
В качестве лучшей практики убедитесь, что вы обрабатываете случай нулевого значения для объектов, допускающих значение NULL, иначе ваше приложение может оказаться в непредвиденных ситуациях. Если ваше приложение больше не будет вылетать с NullPointerException , вы даже не будете знать о существовании этих ошибок.
Ниже приведены несколько способов проверки на наличие значения null:
ifпроверяетval length = if(string != null) string.length else 0Благодаря функции интеллектуального приведения типов и проверке на null, компилятор Kotlin знает, что строковое значение не равно null, поэтому он позволяет использовать ссылку напрямую, без необходимости использования оператора безопасного вызова.
Этот оператор позволяет указать: «Если объект не равен null, вернуть объект; в противном случае вернуть что-то другое».
val length = string?.length ?: 0
В Kotlin по-прежнему может возникнуть ошибка NullPointerException . Ниже перечислены наиболее распространенные ситуации:
- Когда вы явно генерируете исключение
NullPointerException. - При использовании оператора проверки на null
!!этот оператор преобразует любое значение в ненулевой тип, выбрасываяNullPointerExceptionесли значение равно null. - При обращении к нулевой ссылке платформенного типа.
Типы платформ
Типы платформ — это объявления объектов, заимствованные из Java. Эти типы обрабатываются особым образом ; проверки на null не так строго соблюдаются, поэтому гарантия отсутствия null такая же, как в Java. При обращении к ссылке на тип платформы Kotlin не вызывает ошибок компиляции, но такие ссылки могут привести к ошибкам во время выполнения. См. следующий пример из документации Kotlin:
val list = ArrayList<String>() // non-null (constructor result) list.add("Item")
val size = list.size // non-null (primitive int) val item = list[0] // platform
type inferred (ordinary Java object) item.substring(1) // allowed, may throw an
// exception if item == null
Kotlin relies on type inference when a platform value is assigned to a Kotlin variable, or you can define what type to expect. The best way to ensure the correct nullability state of a reference coming from Java is to use nullability annotations (for example, @Nullable ) in your Java code. The Kotlin compiler will represent these references as actual nullable or non-nullable types, not as platform types.
API Java Jetpack аннотированы с помощью @Nullable или @NonNull по мере необходимости, и аналогичный подход используется в SDK Android 11. Типы из этого SDK, используемые в Kotlin, будут представлены как корректные типы, допускающие или не допускающие значение null.
Система типов Kotlin значительно снижает количество сбоев, NullPointerException . Например, в приложении Google Home количество сбоев, вызванных исключениями NullPointerException, сократилось на 30% за год, в течение которого разработка новых функций была переведена на Kotlin.