Эта серия статей посвящена мониторингу производительности и стабильности работающих 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
  }
}

Подтверждение данными

Я реализовал оба подхода определения холодного запуска в тестовом приложении и получил идентичные результаты во всех случаях. Затем я добавил код обнаружения в приложение в продакшене с достаточным количеством установок, чтобы обеспечить значимые результаты. Вот полученные мной (анонимизированные) результаты:

На этой круговой диаграмме отражены только запуски приложений, в которых активити было создано до первого поста.

  • 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 упрощает жизнь с ними. Регистрация доступна по ссылке.

Комментарии (0)