Всем приветЪ! Этот пост является продолжением поста про глубокое погружение в процесс загрузки-запуска Android-приложения. Сегодня мы пойдем чуть дальше и обсудим момент когда главная Activity приложения запущена и система должна отрисовать первый кадр. Прошу под кат.
Следуя официальной документации запущенный процесс приложения отвечает за выполнение следующих шагов:
- Создание объекта класса Application.
- Запуск основного потока(MainThread aka UiThread).
- Создание стартового Activity, который указан в манифесте.
- Расширение(раздутие, inflating) вьюшек. То есть создание вьюшек, которые прописаны в xml-файле.
- Планировка размеров(View.measure()) и размещения(View.layout()) вьюшек на экране.
- Выполнение начальной отрисовки.
После того как был отрисован первый кадр, системный процесс заменяет отображаемое фоновое окно, заменяя его на 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 по иерархии вьюшек:
Заключение
Мы начали с высокого уровня понимания того, что происходит, когда система создает процесс приложения:
Теперь мы знаем что именно происходит под капотом:
А теперь давайте соединим диаграммы из предыдущего поста, с того момента когда пользователь тапает на иконку приложения до момента отрисовки первого кадра:
Теперь, когда у нас есть полная картина, мы можем начать разбираться в том, как правильно контролировать холодный запуск. Следующий пост будет именно об этом! До встречи =)