Разработка
Анализ сведений о запуске и завершении приложений в Android 15
С помощью этих данных можно проследить, как пользователи используют приложение. С помощью данных о выходе можно было проверить, не осталось ли в приложении проблем с выходом, а теперь и то, нет ли проблем с входом.
В Android 11 появилась ApplicationExitInfo
, из которой можно узнать исторические причины, по которым приложение было выключено/завершено.
В Android 15 реализована новая ApplicationStartInfo
, которую можно использовать для анализа причин того, почему и как приложение было запущено.
С помощью этих данных можно проследить, как пользователи используют приложение. С помощью данных о выходе можно было проверить, не осталось ли в приложении проблем с выходом, а теперь и то, нет ли проблем с входом.
ApplicationStartInfo
Как уже было сказано, ApplicationStartInfo
была представлена в Android 15, поэтому она будет доступна только для устройств с API 35 и выше. Поэтому обязательно используйте соответствующую обработку версий или пометьте все методы с помощью @RequiresApi(api = Build.VERSION_CODES.VANILLA_ICE_CREAM)
.
Чтобы получить доступ к данным о стартах вам нужно сделать следующее:
val activityManager: ActivityManager =
context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
val startInfos = activityManager.getHistoricalProcessStartReasons(maxNumberOfInstances)
Получите ActivityManager
из системных служб и с его помощью извлеките столько исторических данных, сколько их доступно в хранилище.
Доступные данные:
- Причина запуска — уведомление, пользователь нажал на иконку в лаунчере, будильник…
- Состояние запуска — состояние, в котором приложение находится в данный момент — ошибка, запущено, первый кадр
- Тип запуска — холодная загрузка, теплая загрузка…
- Режим запуска — способ повторного использования или замены текущей активити — singleInstance, singleTask, singleTop, standard…
- Принудительная остановка — если запуск является первым с тех пор, как приложение было принудительно остановлено
- Интент — намерение, использованное для запуска активности
- Время запуска — карта временных меток событий во время запуска в наносекундах
xxxxxxxxxx
val startType = startInfo.startType
val startReason = startInfo.reason
val startUpState = startInfo.startupState
val launchMode = startInfo.launchMode
val startTimestamp = startInfo.startupTimestamps
val wasForceStopped = startInfo.wasForceStopped()
val startIntent = startInfo.intent
Большинство значений — целые числа, которые определены как статические целочисленные константы в ApplicationStartInfo
.
Добавляем колбек
Вы можете добавить функцию обратного вызова, чтобы получать информацию, когда приложение полностью запущено, и получать информацию о запуске сразу, без запроса истории. Она возвращает список ApplicationStartInfo
.
xxxxxxxxxx
val executor = context.mainExecutor
activityManager.addApplicationStartInfoCompletionListener(executor) {
// access the ApplicationStartInfo via `it`
}
Добавляем события
Вы можете добавлять свои события в историю с помощью своего набора констант, которые должны быть больше 20 (START_TIMESTAMP_RESERVED_RANGE_SYSTEM) и меньше или равны 30 (START_TIMESTAMP_RESERVED_RANGE_DEVELOPER), согласно документации.
Время указывается в наносекундах.
xxxxxxxxxx
val currentTimeInNanos = System.nanoTime()
activityManager.addStartInfoTimestamp(25, timestamp)
System.nanoTime()
не ссылается на временные метки Unix, но если у вас есть 2 или более временных меток, это дает вам более детальное представление о производительности.
ApplicationExitInfo
Чтобы сделать эту статью полной, Android предоставляет способ получить информацию о том, как было завершено приложение. Он возвращает список ApplicationExitInfo
.
xxxxxxxxxx
val exitInfos = activityManager.getHistoricalProcessExitReasons(packageName, 0, maxNum)
Работает почти так же, но вам нужно указать имя пакета из контекста, а также идентификатор процесса (PID), который можно оставить равным 0 для всех записей, и максимальное количество причин выхода, которые нужно получить.
Доступные данные:
- Причина выхода — причина, по которой приложение было остановлено
- Описание — человекочитаемая причина, по которой приложение было остановлено
- Важность выхода — насколько важным было приложение, когда оно было остановлено — передний план, дремота…
- Время остановки — временная метка в миллисекундах, когда приложение было остановлено
Причины выхода — это опять же в основном статические константные целые числа, определенные в ApplicationExitInfo
и ActivityManager.RunningAppProcessInfo
.
Пример использования
Большинство состояний и причин являются целыми числами, определенными в соответствующих классах как статические константы, поэтому мы обычно хотим использовать оператор when
для классификации результата.
Я предпочитаю использовать перечисления, поскольку целые числа не определяют ограничений, а операторы не могут быть исчерпывающими. Кроме того, их можно красиво сериализовать с помощью сериализации Kotlin.
Пример для начала:
xxxxxxxxxx
enum class StartReason {
START_REASON_ALARM,
START_REASON_BACKUP,
START_REASON_BOOT_COMPLETE,
START_REASON_BROADCAST,
START_REASON_CONTENT_PROVIDER,
START_REASON_JOB,
START_REASON_LAUNCHER,
START_REASON_LAUNCHER_RECENTS,
START_REASON_OTHER,
START_REASON_PUSH,
START_REASON_SERVICE,
START_REASON_START_ACTIVITY;
companion object {
fun fromValue(value: Int): StartReason = when (value) {
ApplicationStartInfo.START_REASON_ALARM -> START_REASON_ALARM
ApplicationStartInfo.START_REASON_BACKUP -> START_REASON_BACKUP
ApplicationStartInfo.START_REASON_BOOT_COMPLETE -> START_REASON_BOOT_COMPLETE
ApplicationStartInfo.START_REASON_BROADCAST -> START_REASON_BROADCAST
ApplicationStartInfo.START_REASON_CONTENT_PROVIDER -> START_REASON_CONTENT_PROVIDER
ApplicationStartInfo.START_REASON_JOB -> START_REASON_JOB
ApplicationStartInfo.START_REASON_LAUNCHER -> START_REASON_LAUNCHER
ApplicationStartInfo.START_REASON_LAUNCHER_RECENTS -> START_REASON_LAUNCHER_RECENTS
ApplicationStartInfo.START_REASON_OTHER -> START_REASON_OTHER
ApplicationStartInfo.START_REASON_PUSH -> START_REASON_PUSH
ApplicationStartInfo.START_REASON_SERVICE -> START_REASON_SERVICE
ApplicationStartInfo.START_REASON_START_ACTIVITY -> START_REASON_START_ACTIVITY
else -> throw IllegalArgumentException("Unknown start reason value: $value")
}
}
}
Аналогичным образом мы можем создать класс данных для отображения временных меток из стартовой информации:
xxxxxxxxxx
data class StartupTimestamps(
val applicationOnCreate: Long? = null,
val bindApplication: Long? = null,
val firstFrame: Long? = null,
val fork: Long? = null,
val fullyDrawn: Long? = null,
val initialRenderThreadFrame: Long? = null,
val launch: Long? = null,
val reservedRangeDeveloper: Long? = null,
val reservedRangeDeveloperStart: Long? = null,
val reservedRangeSystem: Long? = null,
val surfaceFlingerCompositionComplete: Long? = null
) {
companion object {
fun fromMap(timestampMap: Map<Int, Long>): StartupTimestamps = StartupTimestamps(
applicationOnCreate = timestampMap[ApplicationStartInfo.START_TIMESTAMP_APPLICATION_ONCREATE],
bindApplication = timestampMap[ApplicationStartInfo.START_TIMESTAMP_BIND_APPLICATION],
firstFrame = timestampMap[ApplicationStartInfo.START_TIMESTAMP_FIRST_FRAME],
fork = timestampMap[ApplicationStartInfo.START_TIMESTAMP_FORK],
fullyDrawn = timestampMap[ApplicationStartInfo.START_TIMESTAMP_FULLY_DRAWN],
initialRenderThreadFrame = timestampMap[ApplicationStartInfo.START_TIMESTAMP_INITIAL_RENDERTHREAD_FRAME],
launch = timestampMap[ApplicationStartInfo.START_TIMESTAMP_LAUNCH],
reservedRangeDeveloper = timestampMap[ApplicationStartInfo.START_TIMESTAMP_RESERVED_RANGE_DEVELOPER],
reservedRangeDeveloperStart = timestampMap[ApplicationStartInfo.START_TIMESTAMP_RESERVED_RANGE_DEVELOPER_START],
reservedRangeSystem = timestampMap[ApplicationStartInfo.START_TIMESTAMP_RESERVED_RANGE_SYSTEM],
surfaceFlingerCompositionComplete = timestampMap[ApplicationStartInfo.START_TIMESTAMP_SURFACEFLINGER_COMPOSITION_COMPLETE]
)
}
}
Или мы можем определить причины выхода:
xxxxxxxxxx
enum class ExitReason {
REASON_ANR,
REASON_CRASH,
REASON_CRASH_NATIVE,
REASON_DEPENDENCY_DIED,
REASON_EXCESSIVE_RESOURCE_USAGE,
REASON_EXIT_SELF,
REASON_FREEZER,
REASON_INITIALIZATION_FAILURE,
REASON_LOW_MEMORY,
REASON_OTHER,
REASON_PACKAGE_STATE_CHANGE,
REASON_PACKAGE_UPDATED,
REASON_PERMISSION_CHANGE,
REASON_SIGNALED,
REASON_UNKNOWN,
REASON_USER_REQUESTED,
REASON_USER_STOPPED;
companion object {
fun fromValue(value: Int): ExitReason = when (value) {
ApplicationExitInfo.REASON_ANR -> REASON_ANR
ApplicationExitInfo.REASON_CRASH -> REASON_CRASH
ApplicationExitInfo.REASON_CRASH_NATIVE -> REASON_CRASH_NATIVE
ApplicationExitInfo.REASON_DEPENDENCY_DIED -> REASON_DEPENDENCY_DIED
ApplicationExitInfo.REASON_EXCESSIVE_RESOURCE_USAGE -> REASON_EXCESSIVE_RESOURCE_USAGE
ApplicationExitInfo.REASON_EXIT_SELF -> REASON_EXIT_SELF
ApplicationExitInfo.REASON_FREEZER -> REASON_FREEZER
ApplicationExitInfo.REASON_INITIALIZATION_FAILURE -> REASON_INITIALIZATION_FAILURE
ApplicationExitInfo.REASON_LOW_MEMORY -> REASON_LOW_MEMORY
ApplicationExitInfo.REASON_OTHER -> REASON_OTHER
ApplicationExitInfo.REASON_PACKAGE_STATE_CHANGE -> REASON_PACKAGE_STATE_CHANGE
ApplicationExitInfo.REASON_PACKAGE_UPDATED -> REASON_PACKAGE_UPDATED
ApplicationExitInfo.REASON_PERMISSION_CHANGE -> REASON_PERMISSION_CHANGE
ApplicationExitInfo.REASON_SIGNALED -> REASON_SIGNALED
ApplicationExitInfo.REASON_UNKNOWN -> REASON_UNKNOWN
ApplicationExitInfo.REASON_USER_REQUESTED -> REASON_USER_REQUESTED
ApplicationExitInfo.REASON_USER_STOPPED -> REASON_USER_STOPPED
else -> throw IllegalArgumentException("Unknown exit reason value: $value")
}
}
}
Не стесняйтесь заменить throw каким-либо значением по умолчанию, если хотите.
Аналогично с другими параметрами вы можете создавать дополнительные перечисления или другие классы данных по своему усмотрению. Для получения более подробной информации посетите документацию — ApplicationStartInfo и ApplicationExitInfo.
Полный пример приложения можно найти в моем репозитории.
Спасибо за чтение!
-
Новости3 недели назад
Видео и подкасты о мобильной разработке 2025.14
-
Видео и подкасты для разработчиков4 недели назад
Исследуем мир фото и видео редакторов
-
Новости4 недели назад
Видео и подкасты о мобильной разработке 2025.13
-
Разработка2 недели назад
Конец продуктовой разработки в том виде, в котором мы ее знаем