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

Для сбоя приложения не обязательно, чтобы оно работало в фоновом режиме. Любой компонент приложения, даже такие компоненты, как широковещательные приемники или поставщики контента, работающие в фоновом режиме, могут привести к сбою приложения. Эти сбои часто сбивают пользователей с толку, поскольку они не взаимодействовали активно с вашим приложением.
Если ваше приложение вылетает, вы можете воспользоваться инструкциями на этой странице, чтобы диагностировать и устранить проблему.
Выявите проблему
Вы не всегда можете знать о сбоях, которые происходят с вашим приложением у пользователей. Если вы уже опубликовали свое приложение, вы можете использовать Android Vitals, чтобы посмотреть частоту сбоев в вашем приложении.
Основные параметры Android
Android Vitals может помочь вам отслеживать и снижать частоту сбоев вашего приложения. Android Vitals измеряет несколько показателей частоты сбоев:
- Показатель сбоев: процент ваших ежедневно активных пользователей, у которых произошел какой-либо сбой.
Показатель количества сбоев, зафиксированных пользователями: процент ваших ежедневно активных пользователей, у которых произошел хотя бы один сбой во время активного использования вашего приложения (сбой, зафиксированный пользователями). Приложение считается активным, если оно отображает какую-либо активность или выполняет какие-либо службы переднего плана .
Показатель частоты множественных сбоев: процент ваших ежедневно активных пользователей, у которых произошло как минимум два сбоя.
Ежедневный активный пользователь — это уникальный пользователь, который использует ваше приложение в течение одного дня на одном устройстве, возможно, в течение нескольких сессий. Если пользователь использует ваше приложение на нескольких устройствах в течение одного дня, каждое устройство будет учитываться в общем количестве активных пользователей за этот день. Если несколько пользователей используют одно и то же устройство в течение одного дня, это считается одним активным пользователем.
Показатель частоты сбоев, воспринимаемый пользователями, является ключевым фактором, влияющим на доступность вашего приложения в Google Play. Это важно, потому что учитываемые сбои всегда происходят, когда пользователь активно взаимодействует с приложением, вызывая наибольшие неудобства.
В Play были определены два пороговых значения для нежелательного поведения по этому показателю:
- Общий порог некорректного поведения: как минимум 1,09% ежедневно активных пользователей сталкиваются с сбоем, зафиксированным пользователем, на всех моделях устройств.
- Порог некорректного поведения для каждого устройства: как минимум 8% ежедневно активных пользователей сталкиваются с сбоем, который, по их мнению , происходит на одном и том же устройстве .
Если ваше приложение превышает общий порог нежелательного поведения, его, вероятно, будет сложнее найти на всех устройствах. Если ваше приложение превышает порог нежелательного поведения для отдельных устройств, его, вероятно, будет сложнее найти на этих устройствах, и в описании вашего приложения в магазине может появиться предупреждение.
Функция Android Vitals может оповещать вас в Play Console , когда ваше приложение часто вылетает.
Информацию о том, как Google Play собирает важные данные об Android, см. в документации Play Console .
Проведите диагностику сбоев.
После того, как вы определили, что ваше приложение сообщает о сбоях, следующим шагом будет их диагностика. Устранение сбоев может быть сложной задачей. Однако, если вы сможете определить первопричину сбоя, скорее всего, вы сможете найти решение.
Существует множество ситуаций, которые могут привести к сбою в вашем приложении. Некоторые причины очевидны, например, проверка на нулевое значение или пустую строку, но другие более сложны, например, передача недопустимых аргументов в API или даже сложные многопоточные взаимодействия.
При сбоях в Android создается трассировка стека, которая представляет собой снимок последовательности вложенных функций, вызываемых в вашей программе до момента сбоя. Вы можете просмотреть трассировку стека при сбоях в Android Vitals .
Как читать трассировку стека
Первый шаг для устранения сбоя — определить место его возникновения. Вы можете использовать трассировку стека, доступную в подробностях отчета, если используете Play Console, или вывод инструмента logcat . Если трассировка стека недоступна, следует воспроизвести сбой локально, либо вручную протестировав приложение, либо связавшись с затронутыми пользователями, и воспроизвести его с помощью 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 или что-то другое, и найдите документацию по классу исключения.
На второй строке трассировки стека указаны класс, метод, файл и номер строки исходного файла, в котором возникло исключение. Для каждой вызванной функции отдельная строка показывает место предыдущего вызова (так называемый кадр стека).
Проанализировав стек вызовов и изучив код, вы можете обнаружить место, где передается некорректное значение. Если ваш код не отображается в трассировке стека, вероятно, где-то вы передали недопустимый параметр в асинхронную операцию. Часто можно выяснить, что произошло, изучив каждую строку трассировки стека, найдя используемые вами классы API и убедившись, что переданные параметры были правильными и что вызов был произведен из разрешенного места.
Трассировка стека для приложений, использующих код на 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. Дополнительную информацию см. в разделе «Деобфускация трассировок стека сбоев». Общую информацию о сбоях нативных приложений см. в разделе «Диагностика сбоев нативных приложений» .
Советы по воспроизведению сбоя
Возможно, вам не удастся воспроизвести проблему, просто запустив эмулятор или подключив устройство к компьютеру. В средах разработки обычно больше ресурсов, таких как пропускная способность, память и хранилище. Используйте тип исключения, чтобы определить, какой ресурс может быть дефицитным, или найдите корреляцию между версией Android, типом устройства или версией вашего приложения.
Ошибки памяти
Если возникает ошибка 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 , которые вы добавили, поскольку их вывод расходует ресурсы процессора и заряд батареи во время работы вашего приложения.
Предотвращение сбоев, вызванных исключениями нулевого указателя.
Исключения типа NullPointerException (идентифицируемые по типу ошибки времени выполнения NullPointerException ) возникают при попытке доступа к объекту, имеющему значение null, обычно при вызове его методов или обращении к его членам. Исключения типа NullPointerException являются основной причиной сбоев приложений в Google Play. Значение null указывает на то, что объект отсутствует — например, он еще не был создан или ему не был присвоен символ.
Чтобы избежать исключений 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 использует вывод типов при присвоении переменной Kotlin значения, соответствующего платформе, или же вы можете определить, какой тип следует ожидать. Лучший способ обеспечить корректное состояние допуска значения null для ссылки, поступающей из Java, — использовать аннотации допуска значения null (например, @Nullable ) в вашем Java-коде. Компилятор Kotlin будет представлять эти ссылки как фактически допускающие или не допускающие значение null типы, а не как типы, соответствующие платформе.
API Java Jetpack аннотированы с помощью @Nullable или @NonNull по мере необходимости, и аналогичный подход используется в SDK Android 11. Типы из этого SDK, используемые в Kotlin, будут представлены как корректные типы, допускающие или не допускающие значение null.
Система типов Kotlin значительно снижает количество сбоев, NullPointerException . Например, в приложении Google Home количество сбоев, вызванных исключениями NullPointerException, сократилось на 30% за год, в течение которого разработка новых функций была переведена на Kotlin.