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

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

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

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

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

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

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

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

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

Важный момент: ничего не происходит в главном потоке процесса приложения пока не выполнится 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().

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

Фактический запуск 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;
      }
    }
  }
}

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

Первый кадр

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

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

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

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

Заключение

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

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

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

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

«Холодный» запуск приложения

Глубокий экскурс в процесс «холодного» запуска Android приложения, с момента нажатия на иконку и до создания процесса приложения.

Continue reading

Вопросы по RxJava на собеседованиях

Ответы на самые частые вопросы по RxJava

Continue reading

HandlerThread

HandlerThread — удобная обертка, которая автоматически создает и настраивает внутренние механизмы передачи сообщений(Thread, Looper, MessageQueue)

Continue reading

Анимации

Виды анимаций в Android

Continue reading

Активити и Фрагменты

Вопросы на собесах

Continue reading

Паттерн ViewHolder

Ваш код может часто вызывать findViewById() во время прокрутки ListView, что может снизить производительность. Даже когда адаптер возвращает раздутое(inflated) представление для повторного использования, вам все равно нужно искать элементы и обновлять их. Способ обойти повторное использование findViewById() — это использовать паттерн ViewHolder.

Continue reading

ViewGroup

Вопросы на собесах

Continue reading

RecyclerView

RecyclerView, по сути, является эволюцией одного из самых необходимых в Android-разработке виджетов — ListView.

Continue reading

Kodein

Коротко о Kodein

Continue reading

© 2020 Nurlandroid

Theme by Anders NorénUp ↑