Русский
Русский
English
Статистика
Реклама

Перевод Отрисовка первого кадра 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

Теперь, когда у нас есть полная картина, мы можем начать разбираться в том, как правильно контролировать холодный запуск. Следующий пост будет именно об этом! До встречи =)
Источник: habr.com
К списку статей
Опубликовано: 09.10.2020 08:13:28
0

Сейчас читают

Комментариев (0)
Имя
Электронная почта

Разработка под android

Android development

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru