Android Vitals — Это холодный старт?

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.

Эта серия блогов посвящена мониторингу стабильности и производительности приложений Android в продакшне. В последних двух постах я описал то, что происходит с момента, когда пользователь нажимает на ярлык запуска программы, до момента отрисовки первой activity на мониторе.

Холодный запуск — это запуск activity, при котором происходит старт процесса приложения с нуля в ответ на намерение запустить activity. Согласно документации по времени запуска приложения:

Этот тип запуска представляет наибольшую проблему с точки зрения минимизации времени старта, поскольку системе и приложениям приходится выполнять больше работы, чем в других состояниях при включении.

Мы рекомендуем всегда проводить оптимизацию на основе предположения о холодном запуске. Это может улучшить показатели теплого и горячего старта.

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

К сожалению, в Android нет API Activity.isThisAColdStart(). Так задумано: API жизненного цикла Activity указывают, когда сохранять и восстанавливать состояние, и абстрагируются от смерти и возрождения процессов. Инженеры, разработавшие API для Android, не хотели, чтобы мы писали слишком сложный код со специальными случаями для всех различных способов запуска activity. Поэтому API не существует.

Как мы должны отслеживать холодный запуск, если не можем отличить его от любого другого запуска процесса?

В этой заметке мы используем то, что выяснили из наших предыдущих глубоких погружений в тему холодного старта, чтобы начать создавать собственную версию отсутствующего API Activity.isThisAColdStart().

Традиционный подход

Большинство приложений и библиотек информируют о холодном старте, если первая Activity была создана в течение минуты после запуска приложения. Это выглядит примерно так:

class MyApp : Application() {

  override fun onCreate() {
    super.onCreate()

    val appCreateMs = SystemClock.uptimeMillis()

    var firstActivityCreated = false

    registerActivityLifecycleCallbacks(object :
        ActivityLifecycleCallbacks {
      override fun onActivityCreated(
          activity: Activity,
          savedInstanceState: Bundle?
      ) {
        if (firstActivityCreated) {
          return
        }
        firstActivityCreated = true
        val activityCreateMs = SystemClock.uptimeMillis()
        if (activityCreateMs - appCreateMs < 60_000) {
          // TODO Report cold start
        }
      }
    })
  }
}

К сожалению, этот подход также включает случаи, когда приложение запускалось для отклика на широковещательный приемник, запрос контент-провайдера или для пуска сервиса, после чего несколько позже, в течение первой минуты, начинала выполняться activity. Необходимо исключить эти случаи из мониторинга холодного запуска, чтобы избежать искажения результатов.

Использование результатов нашего исследования

В предыдущей статье блога мы узнали, что:

  • Когда запускается процесс приложения, он вызывает ActivityThread.main(), который выполняет блокирующий вызов IPC для ActivityManagerService.attachApplication() в процессе system_server.

  • Процесс system_server выполняет IPC-вызов для  ActivityThread.bindApplication() в процессе приложения, который ставит сообщение BIND_APPLICATION в очередь сообщений основного потока.

  • Затем для каждой activity, которую необходимо запустить, процесс system_server выполняет IPC-вызов для  ActivityThread.scheduleTransaction() в процессе приложения, который регистрирует сообщение EXECUTE_TRANSACTION в очереди сообщений главного потока.

  • Когда IPC вызов для  ActivityManagerService.attachApplication() завершен, ActivityThread.main() затем вызывает Looper.loop(), который зацикливается и обрабатывает сообщения, отправленные в его MessageQueue.

  • Первым обрабатывается сообщение BIND_APPLICATION, которое вызывает ActivityThread.handleBindApplication(), который вызывает Application.onCreate().

  • Следующим обрабатываемым сообщением является EXECUTE_TRANSACTION, которое вызывает TransactionExecutor.execute(), запускающий activity.

Это означает, что в Application.onCreate() в очереди сообщений главного потока уже зарегистрировано сообщение EXECUTE_TRANSACTION. Если мы отправим новое сообщение из Application.onCreate(), оно будет выполнено после EXECUTE_TRANSACTION и, следовательно, после создания activity. Если мы публикуем сообщение, а в момент его выполнения activity не была создана, тогда понятно, что это не холодный старт, даже если activity в итоге будет запущена через 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 Report cold start
      }
    }
  }
}

Теплый запуск

Согласно документации по времени запуска приложения:

Существует множество потенциальных состояний, которые могут считаться теплым запуском. Например:

  • Пользователь выходит из вашего приложения, но затем снова запускает его. Процесс может продолжать выполняться, но приложение должно воссоздать activity с нуля через вызов onCreate().

  • Система вытесняет ваше приложение из памяти, а затем пользователь снова запускает его. Процесс и activity должны быть перезапущены, но при этом задача может получить некоторую выгоду от сохраненного пакета состояний инстанса, переданного в onCreate().

Таким образом, если activity создается с сохраненным пакетом состояний инстанса, то это не должно считаться холодным стартом. Поскольку процесс должен быть перезапущен, необходимо проделать гораздо больше работы, чем просто создать activity. Назовем это "теплым стартом".

Чтобы это учесть, можно обновить наш код:

class MyApp : Application() {

  override fun onCreate() {
    super.onCreate()

    var firstActivityCreated = false
    var hasSavedState = false

    registerActivityLifecycleCallbacks(object :
        ActivityLifecycleCallbacks {

      override fun onActivityCreated(
          activity: Activity,
          savedInstanceState: Bundle?
      ) {
        if (firstActivityCreated) {
          return
        }
        firstActivityCreated = true
        hasSavedState = savedInstanceState != null
      }
    })
    Handler().post {
      if (firstActivityCreated) {
        if (hasSavedState) {
          // TODO Report lukewarm start
        } else {
          // TODO Report cold start
        }
      }
    }
  }
}

Заключение

Команда платформы Android не хочет, чтобы нам приходилось ломать голову над запуском процессов и activity, однако команда разработчиков улучшения производительности приложений Android настаивает на оптимизации холодного старта. Вызов принят! Мы начинаем создавать свою собственную версию отсутствующего API Activity.isThisAColdStart(), но до завершения работы еще далеко. Оставайтесь с нами, чтобы узнать больше!


Материал подготовлен в рамках специализации «Android Developer».

Всех желающих приглашаем на бесплатный интенсив «Делаем упрощенный аналог приложения Notion». В рамках двухдневного интенсива мы сделаем упрощенный аналог приложения Notion для платформы Android.

Этот урок подойдет для тех, кто:
- хочет попробовать себя в качестве Андроид-разработчика
- уже знаком с программированием и знает принципы ООП
- имеет опыт программирования для других платформ
>> РЕГИСТРАЦИЯ

Источник: https://habr.com/ru/company/otus/blog/593743/


Интересные статьи

Интересные статьи

Меня зовут, Денис я работаю в компании Домклик. Как вы уже догадались из названия, в этой статье речь пойдёт о таком важном элементе любого веб-сервиса, как производитель...
Всем привет! Я пишу приложения под Android, в мире которого система сборки Gradle является стандартом де-факто. Я решил поделиться некоторыми советами по работе с Gradle с теми, у кого не...
Здравствуйте уважаемые читатели. Все мы любим и используем DataBinding, который представила компания Google, для связи моделей данных с вьюшками через ViewModel. В этой с...
В этой статье мы расскажем, как оптимизировать крупный проект в «Битрикс24» и увеличить его производительность в 3 раза, изменяя настройки MySQL и режим питания CPU. Дано Корпоративн...
Написано огромное количество документации и статей о важной визуальной составляющей приложений — анимации. Несмотря на это мы смогли вляпаться в проблемы столкнулись с загвоздками при...