Эта серия статей посвящена мониторингу производительности и стабильности Android-приложений в эксплуатационной среде. В прошлой статье автор писал об измерении времени в Android Vitals — Сколько времени? В следующих статьях серии будет рассказано, как мониторить холодный запуск. Согласно документации по этапу запуска приложения: Холодный запуск - это запуск приложения с нуля: до этого запуска системный процесс еще не создавал процесс приложения. Холодный запуск происходит, когда ваше приложение запускается впервые после загрузки устройства или после того, как система завершила работу приложения. В начале холодного запуска у системы есть 3 задачи: Загрузка и запуск приложения. Отображение стартового окна. Создание процесса приложения. В этой статье мы с вами подробно разберем начало холодного запуска, от тапа по иконке лаунчера до создания процесса приложения. Диаграмма, созданная с помощью WebSequenceDiagram. Когда пользователь тапает по иконке лаунчера, процесс запуска приложения вызывает Activity.startActivity(), делегируя выполнение в Instrumentation.execStartActivity(): Затем процесс запуска приложения выполняет IPC-вызов ActivityTaskManagerService.startActivity() процесса system_server. Процесс system_server хостит большинство системных служб. Перед созданием нового процесса приложения процесс system_server создает стартовое окно посредством PhoneWindowManager.addSplashScreen(): Стартовое окно - это то, что видит пользователь во время запуска процесса приложения, ровно до тех пор, пока он не создаст свою activity и не отобразит свой первый кадр. То есть до тех пор, пока не будет завершен холодный запуск. Пользователь может любоваться стартовым окном достаточно долго, поэтому убедитесь, что оно выглядит очень хорошо . Содержимое стартового окна загружается из графических ресурсов (drawables) windowSplashscreenContent и windowBackground запущенного activity. Чтобы узнать об этом больше, почитайте статью Android App Launching Made Gorgeous. Если пользователь возвращает activity с экранного менеджера, а не посредством тапа по лаунчеру, процесс system_server вызывает TaskSnapshotSurface.create(), который создает стартовое окно из ранее сохраненного снапшота этого activity. Как только стартовое окно отображено, процесс system_server готов к запуску процесса приложения, для чего он вызывает ZygoteProcess.startViaZygote(): ZygoteProcess.zygoteSendArgsAndGetResult() отправляет процессу Zygote аргументы для запуска через сокет. Согласно документации Android по управлению памятью: Процесс каждого приложения является форком уже существующего процесса под названием Zygote. Процесс Zygote запускается на старте системы, когда загружает общий системный код и ресурсы (например, темы activity). Чтобы запустить процесс нового приложения, система форкает этот процесс от Zygote, а затем загружает и запускает в нем код этого приложения. Такой подход позволяет использовать большую часть страниц RAM, выделенных под системный код и ресурсы, сразу всем процессам приложений. Во время загрузки системы запускается процесс Zygote и вызывается ZygoteInit.main(): Как видите, ZygoteInit.main() выполняет 2 важные функции: Он предзагружает системные классы и ресурсы Android, общие библиотеки, графические драйверы и т. д. Эта предзагрузка не только экономит память, но и сокращает время запуска. Затем он вызывает ZygoteServer.runSelectLoop(), который открывает сокет и ждет. Когда по этому сокету получена команда к форку, ZygoteConnection.processOneCommand() парсит аргументы с помощью ZygoteArguments.parseArgs() и вызывает Zygote.forkAndSpecialize(): Примечание: В Android 10 добавлена ​​поддержка оптимизации под названием Unspecialized App Process (USAP), пула форков Zygote, ожидающих своей специализации. Запуск стал чуть быстрее за счет дополнительного потребленя памяти (по умолчанию эта фича отключена). Android 11 поставляется с IORap,что дает гораздо лучшие результаты. После форка дочерней процесс приложения запускает RuntimeInit.commonInit(), который устанавливает дефолтный UncaughtExceptionHandler. Затем процесс приложения запускает ActivityThread.main(): Здесь есть два интересных момента: ActivityThread.main() вызывает Looper.loop(), который повторяется бесконечно, ожидая поступления новых сообщений в его MessageQueue. ActivityThread.attach() совершает IPC-вызов ActivityManagerService.attachApplication() процесса system_server, чтобы сообщить ему, что основной поток (main thread) приложения готов к работе 🚀. В процессе system_server, ActivityManagerService.attachApplication() вызывает ActivityManagerService.attachApplicationLocked(), который завершает настройку приложения: Несколько ключевых моментов: Процесс system_server совершает IPC-вызов к ActivityThread.bindApplication() процесса приложения, который планирует вызов ActivityThread.handleBindApplication() основного потока приложения. Сразу после этого процесс system_server планирует запуск любой ожидающей activity, службы и широковещательного приемника (broadcast receiver). ActivityThread.handleBindApplication() загружает APK и компоненты приложения в следующем порядке: Загружает подкласс приложения AppComponentFactory и создает инстанс. Вызывает AppComponentFactory.instantiateClassLoader(). Вызывает AppComponentFactory.instantiateApplication(), чтобы загрузить подкласс приложения Application и создать инстанс. Для каждого объявленного ContentProvider в порядке приоритета вызывает AppComponentFactory.instantiateProvider(), чтобы загрузить его класс и создать инстанс, затем вызывает ContentProvider.onCreate(). Вызывает Application.onCreate(). Разработчики приложений практически не могут повлиять на время, затрачиваемое перед ActivityThread.handleBindApplication(), поэтому именно с этого места следует начинать мониторинг холодного запуска приложения. Если вам нужно запустить код как можно раньше, у вас есть несколько вариантов: Самый ранний вариант - это когда загружается класс AppComponentFactory. Добавьте атрибут appComponentFactory к тегу приложения в AndroidManifest.xml. Если вы используете AndroidX, вам необходимо добавить tools:replace="android:appComponentFactory" и делегировать вызовы в AndroidX AppComponentFactory. Вы можете добавить туда статический инициализатор и выполнять такие действия, как сохранение таймстемпов. Недостатки: это доступно только в Android P+, и у вас не будет доступа к контексту. Безопасный ранний вариант для разработчиков приложений - Application.onCreate(). Надежный ранний вариант для разработчиков библиотек - ContentProvider.onCreate(). Этот трюк популяризировал Дуг Стивенсон (Doug Stevenson) в своей книге “Как инициализируется Firebase на Android?” Есть новая AndroidX App Startup library, основанная на том же приеме с провайдером. Цель состоит в том, чтобы объявить только один провайдер вместо нескольких, потому что каждый объявленный провайдер замедляет запуск приложения на несколько миллисекунд и увеличивает размер объекта ApplicationInfo из диспетчера пакетов. Мы начали с высокого уровня понимания того, как начинается холодный запуск: Теперь мы точно представляем, что происходит: Пользовательский опыт запуска activity начинается, когда пользователь касается экрана, однако разработчики приложений мало влияют на время до ActivityThread.handleBindApplication(), так что именно здесь должен начинаться мониторинг холодного запуска приложения. Эта статья получилась достаточно длинной, а мы еще далеки от завершения холодного старта. Следите за обновлениями! Перевод статьи подготовлен в преддверии старта курса Android Developer. Professional. Узнать подробнее о курсе Редактор