Эта серия статей посвящена мониторингу производительности и стабильности работающих Android-приложений. На прошлой неделе я писал о том, как определить, является ли запуск приложения холодным.
Если мы постим сообщение, и в момент его выполнения не будет создано никаких активити, то так мы поймем, что это не холодный запуск, даже если в конечном итоге активити запустится через 20 секунд.
class MyApp : Application() {
override fun onCreate() {
super.onCreate()
var firstActivityCreated = false
registerActivityLifecycleCallbacks(object :
ActivityLifecycleCallbacks {
override fun onActivityCreated(
activity: Activity,
savedInstanceState: Bundle?
) {
if (firstActivityCreated) {
return
}
firstActivityCreated = true
}
})
Handler().post {
if (firstActivityCreated) {
// TODO рапортуем о холодном запуске
}
}
}
}
Такой подход предполагает, что мы должны дождаться запуска активити или выполнения этого сообщения, прежде чем узнаем, был ли запуск приложения холодным. Иногда было бы полезно знать это прямо внутри Application.onCreate()
. Например, нам может понадобиться асинхронная предварительная загрузка ресурсов для оптимизации холодного запуска:
class MyApp : Application() {
override fun onCreate() {
super.onCreate()
if (isColdStart()) {
preloadDataForUiAsync()
}
}
}
Важность процесса
Хоть пока и нет Android API для того, чтобы узнать, почему процесс был запущен, у нас все-таки есть способ узнать, почему процесс все еще работает:
RunningAppProcessInfo.importance, которое мы можем прочитать из ActivityManager.getMyMemoryState(). Согласно документации “Процессы и жизненный цикл приложений”:
Чтобы определить, какие процессы следует завершить в случае нехватки памяти, Android помещает каждый процесс в “иерархию важности” на основе запущенных в них компонентов и их состояний. [...] Система будет основывать свое решение о том, как классифицировать процесс, на наиболее важном уровне, обнаруженном среди всех компонентов, активных в процессе в данный момент.
Когда процесс запускается, мы можем проверить его важность. Если его важность оценена как IMPORTANCE_FOREGROUND, то это холодный запуск:
class MyApp : Application() {
override fun onCreate() {
super.onCreate()
if (isForegroundProcess()) {
preloadDataForUiAsync()
}
}
private fun isForegroundProcess(): Boolean {
val processInfo = ActivityManager.RunningAppProcessInfo()
ActivityManager.getMyMemoryState(processInfo)
return processInfo.importance == IMPORTANCE_FOREGROUND
}
}
Подтверждение данными
Я реализовал оба подхода определения холодного запуска в тестовом приложении и получил идентичные результаты во всех случаях. Затем я добавил код обнаружения в приложение в продакшене с достаточным количеством установок, чтобы обеспечить значимые результаты. Вот полученные мной (анонимизированные) результаты:
На этой круговой диаграмме отражены только запуски приложений, в которых активити было создано до первого поста.
97,4% имели важность 100, т.е. IMPORTANCE_FOREGROUND.
2,4% имели важность 400, т.е. IMPORTANCE_CACHED (ранее называлось IMPORTANCE_BACKGROUND).
0,2% соответствуют другим значениям.
Давайте посмотрим на эти данные под другим углом: как часто активити создавалось до первого поста по каждому значению важности?
Когда важность запуска равна 100, перед первым постом всегда создается активити. И когда активити создается до первого сообщения, в 97,4% случаев важность равна 100.
Когда важность запуска равна 400, активити не создается до первого поста почти никогда. “Почти никогда” это не “никогда” — все же достаточно случаев, когда активити создается до первого поста. В 2,4% случаев важность равна 400.
Наиболее вероятное объяснение 2,4% с важностью 400 заключается в том, что это были не холодные запуски, однако система получила запрос на запуск активити почти сразу после запуска процесса, прямо на запуске Application.onCreate()
, но до того, как у нас появилась возможность добавить наш первый пост.
Поправка: я сохранял в логе важность после первого поста и сравнивал ее с важностью на запуске приложения. Мои данные показали, что в 74% запусков приложений, когда активити было создано до первого поста, и важность процесса запуска изначально не была равна 100, после первого поста значение важности этого процесса менялось на 100. Мне кажется что, это подтверждает теорию о том, что система решила запустить активити после того, как приложение уже начало запускаться.
Заключение
Теперь на основе этой информации и выводов из предыдущего поста мы можем точно определить холодный запуск. Для холодного запуска:
Важность процесса соответствует IMPORTANCE_FOREGROUND на запуске приложения.
Первое активити создается до того, как первый пост будет исключен из очереди.
Первое активити создается с нулевым бандлом.
Вот обновленный код:
class MyApp : Application() {
override fun onCreate() {
super.onCreate()
if (isForegroundProcess()) {
var firstPostEnqueued = true
Handler().post {
firstPostEnqueued = false
}
registerActivityLifecycleCallbacks(object :
ActivityLifecycleCallbacks {
override fun onActivityCreated(
activity: Activity,
savedInstanceState: Bundle?
) {
unregisterActivityLifecycleCallbacks(this)
if (firstPostEnqueued && savedInstanceState == null) {
// TODO рапортуем о холодном запуске
}
}
})
}
}
private fun isForegroundProcess(): Boolean {
val processInfo = ActivityManager.RunningAppProcessInfo()
ActivityManager.getMyMemoryState(processInfo)
return processInfo.importance == IMPORTANCE_FOREGROUND
}
}
Надеюсь, для вас эти исследования были интересными!
Всех желающих приглашаем на двухдневный интенсив «Animated Vector Drawable», на котором поговорим про векторные изображения в Android и о том, как shapeshifter упрощает жизнь с ними. Регистрация доступна по ссылке.