Этот пост является продолжением поста про глубокое погружение в процесс загрузки-запуска 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 по иерархии вьюшек:
Заключение
Мы начали с высокого уровня понимания того, что происходит, когда система создает процесс приложения:
Теперь мы знаем что именно происходит «под капотом»:
А теперь давайте соединим диаграммы из предыдущего поста, с того момента когда пользователь тапает на иконку приложения до момента отрисовки первого кадра:
Теперь, когда у нас есть полная картина, мы можем начать разбираться в том, как правильно контролировать холодный запуск. Следующий пост будет именно об этом! До встречи =)