Отрисовка первого кадра Android-приложения

от автора

image

Всем приветЪ! Этот пост является продолжением поста про глубокое погружение в процесс загрузки-запуска Android-приложения. Сегодня мы пойдем чуть дальше и обсудим момент когда главная Activity приложения запущена и система должна отрисовать первый кадр. Прошу под кат.

Следуя официальной документации запущенный процесс приложения отвечает за выполнение следующих шагов:

  1. Создание объекта класса Application.
  2. Запуск основного потока(MainThread aka UiThread).
  3. Создание стартового Activity, который указан в манифесте.
  4. Расширение(раздутие, inflating) вьюшек. То есть создание вьюшек, которые прописаны в xml-файле.
  5. Планировка размеров(View.measure()) и размещения(View.layout()) вьюшек на экране.
  6. Выполнение начальной отрисовки.

После того как был отрисован первый кадр, системный процесс заменяет отображаемое фоновое окно, заменяя его на Activity приложения. Теперь пользователь может взаимодействовать с приложением.

image

А теперь давайте по подробнее обо всех шагах.

Старт главного потока

В предыдущем посте мы узнали:

  • Когда запускается процесс приложения, он вызывает метод ActivityThread.main(), который делает блокирующий IPC-запрос к методу ActivityManagerService.attachApplication() в процессе system_server.
  • system_server делает IPC-вызов в процессе приложения метода ActivityThread.bindApplication(), который ставит в очередь сообщение BIND_APPLICATION в MessageQueue главного потока.
  • Когда IPC-вызов метода ActivityManagerService.attachApplication() завершен, ActivityThread.main() вызывает Looper.loop(), который будет зациклен навсегда(пока приложение работает) и будет обрабатывать сообщения поступающие в MessageQueue.
  • Первое сообщение, которое будет обработано это BIND_APPLICATION. В этот момент будет вызван метод ActivityThread.handleBindApplication(), который загрузит APK и другие компоненты приложения.

image

Важный момент: ничего не происходит в главном потоке процесса приложения пока не выполнится IPC-вызов метода ActivityManagerService.attachApplication().

Планируем запуск Activity

Давайте посмотрим что происходит в процессе system_server после вызова метода ActivityThread.bindApplication():

public class ActivityManagerService extends IActivityManager.Stub {    private boolean attachApplicationLocked(       IApplicationThread thread, int pid, int callingUid,       long startSeq) {     thread.bindApplication(...);      // See if the top visible activity is waiting to run     //  in this process...     mAtmInternal.attachApplication(...);      // Find any services that should be running in this process...     mServices.attachApplicationLocked(app, processName);      // Check if a next-broadcast receiver is in this process...     if (isPendingBroadcastProcessLocked(pid)) {         sendPendingBroadcastsLocked(app);     }     return true;   } }

Строка которая релевантна запуску Activity — mAtmInternal.attachApplication(…). Метод вызывает ActivityTaskManagerService.attachApplication(), который в свою очередь вызывает метод RootActivityContainer.attachApplication():

class RootActivityContainer extends ConfigurationContainer {    boolean attachApplication(WindowProcessController app) {     for (ActivityDisplay display : mActivityDisplays) {       ActivityStack stack = display.getFocusedStack()       ActivityRecord top = stack.topRunningActivityLocked();       stack.getAllRunningVisibleActivitiesLocked(mTmpActivityList);       for (ActivityRecord activity : mTmpActivityList) {         if (activity.app == null             && app.mUid == activity.info.applicationInfo.uid             && app.mName.equals(activity.processName)) {           mStackSupervisor.realStartActivityLocked(             activity,             app,             top == activity /* andResume */,             true /* checkConfig */           )         }       }     }     ...   } }

Код делает следующее:

  • Обходит каждый дисплей.
  • Получает стек сфокусированных Activity для этого дисплея.
  • Проходит по каждому Activity целевого стека Activity.
  • Если Activity принадлежит к запущенному процессу, то вызывается метод ActivityStackSupervisor.realStartActivityLocked(). Обратите внимание, что параметр andResume будет иметь значение true если Activity находится на вершине стэка.

Вот как выглядит метод ActivityStackSupervisor.realStartActivityLocked():

public class ActivityStackSupervisor{    boolean realStartActivityLocked(     ActivityRecord r,     WindowProcessController proc,     boolean andResume,     boolean checkConfig   ) {     ...     ClientTransaction clientTransaction = ClientTransaction.obtain(             proc.getThread(), r.appToken);      clientTransaction.addCallback(LaunchActivityItem.obtain(...));      // Set desired final state.     final ActivityLifecycleItem lifecycleItem;     if (andResume) {         boolean forward = dc.isNextTransitionForward()         lifecycleItem = ResumeActivityItem.obtain(forward);     } else {         lifecycleItem = PauseActivityItem.obtain();     }     clientTransaction.setLifecycleStateRequest(lifecycleItem);      // Schedule transaction.     mService.getLifecycleManager()       .scheduleTransaction(clientTransaction);     ...   } }

Все вызовы методов, которые мы просмотрели происходят в системном процессе system_server. Метод ClientLifecycleManager.scheduleTransaction() делает IPC-вызов ActivityThread.scheduleTransaction() в процессе приложения, который вызывает ClientTransactionHandler.scheduleTransaction(), чтобы положить в очередь сообщение EXECUTE_TRANSACTION:

public abstract class ClientTransactionHandler {      /** Prepare and schedule transaction for execution. */     void scheduleTransaction(ClientTransaction transaction) {         transaction.preExecute(this);         sendMessage(           ActivityThread.H.EXECUTE_TRANSACTION,           transaction         );     } }

При обработке сообщения EXECUTE_TRANSACTION происходит вызов метода TransactionExecutor.execute().

Теперь можно обновить диаграмму начальной последовательного запуска:

image

Фактический запуск Activity

Метод TransactionExecutor.execute() вызывает TransactionExecutor.
performLifecycleSequence(), который в свою очередь делает коллбэк в ActivityThread для создания(create), запуска(start) и продолжения(resume) Activity:

public class TransactionExecutor {    private void performLifecycleSequence(...) {     for (int i = 0, state; i < path.size(); i++) {       state = path.get(i);       switch (state) {         case ON_CREATE:           mTransactionHandler.handleLaunchActivity(...);           break;         case ON_START:           mTransactionHandler.handleStartActivity(...);           break;         case ON_RESUME:           mTransactionHandler.handleResumeActivity(...);           break;         case ON_PAUSE:           mTransactionHandler.handlePauseActivity(...);           break;         case ON_STOP:           mTransactionHandler.handleStopActivity(...);           break;         case ON_DESTROY:           mTransactionHandler.handleDestroyActivity(...);           break;         case ON_RESTART:           mTransactionHandler.performRestartActivity(...);           break;       }     }   } }

Обновляем диаграмму:

image

Первый кадр

Давайте взглянем на последовательность вызовов методов, которые ведут к отрисовке первого кадра:

  • ActivityThread.handleResumeActivity()
  • WindowManagerImpl.addView()
  • WindowManagerGlobal.addView()
  • ViewRootImpl.setView()
  • ViewRootImpl.requestLayout()
  • ViewRootImpl.scheduleTraversals()
  • Choreographer.postCallback()
  • Choreographer.scheduleFrameLocked()

Метод Choreographer.scheduleFrameLocked() ставит в очередь сообщение MSG_DO_FRAME:

image

При обработке сообщения MSG_DO_FRAME происходит вызов метода Choreographer.doFrame(), который в свою очередь вызывает ViewRootImpl.doTraversal(), который осуществляет проходы measure pass и layout pass, и наконец проход the first draw pass по иерархии вьюшек:

image

Заключение

Мы начали с высокого уровня понимания того, что происходит, когда система создает процесс приложения:

image

Теперь мы знаем что именно происходит «под капотом»:

image

А теперь давайте соединим диаграммы из предыдущего поста, с того момента когда пользователь тапает на иконку приложения до момента отрисовки первого кадра:

image

Теперь, когда у нас есть полная картина, мы можем начать разбираться в том, как правильно контролировать холодный запуск. Следующий пост будет именно об этом! До встречи.

ссылка на оригинал статьи https://habr.com/ru/post/522670/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *