Header image: Flying in the Light by Romain Guy.
This blog series is focused on stability and performance monitoring of Android apps in production. Last week, I wrote about the beginning of a cold start, from tapping a launcher icon to the creation of the app process.
The App startup time documentation outlines the next stages:
As soon as the system creates the app process, the app process is responsible for the next stages:
- Creating the app object.
- Launching the main thread.
- Creating the main activity.
- Inflating views.
- Laying out the screen.
- Performing the initial draw.
Once the app process has completed the first draw, the system process swaps out the currently displayed background window, replacing it with the main activity. At this point, the user can start using the app.
Diagram created with WebSequenceDiagram.
This post is a continuation of our deep dive on cold start, where we'll rise from the launch of the first activity to the first draw on a surface.
Main thread start
In the previous blog post, we learnt that:
- When the app process starts, it calls ActivityThread.main() which makes a blocking IPC call to ActivityManagerService.attachApplication() in the
system_server
process. - The
system_server
process makes an IPC call to ActivityThread.bindApplication() in the app process, which enqueues aBIND_APPLICATION
message to the main thread message queue. - When the IPC call to ActivityManagerService.attachApplication() is done, ActivityThread.main() then calls Looper.loop() which loops forever, processing messages posted to its MessageQueue.
- The first message processed is
BIND_APPLICATION
, which calls ActivityThread.handleBindApplication() which loads the APK and loads the app components.
An important take away here is that nothing happens in the app process main thread until the IPC call to ActivityManagerService.attachApplication() returns.
Scheduling activity launch
Let's look at what happens in the system_server
process after calling 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;
}
}
The line relevant to activity launch is mAtmInternal.attachApplication(...)
. It calls ActivityTaskManagerService.attachApplication() which calls 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 */
)
}
}
}
...
}
}
The above code:
- Iterates over each display.
- Retrieves the focused activity stack for that display.
- Iterates on each activity of the focused activity stack.
- Calls ActivityStackSupervisor.realStartActivityLocked() if that activity belongs to the process being started. Notice the
andResume
parameter passed as true if the activity is at the top of the stack.
Here's 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);
...
}
}
All the method calls we've looked at so far are happening in the system_server
process. ClientLifecycleManager.scheduleTransaction() makes an IPC call to ActivityThread.scheduleTransaction() in the app process, which calls ClientTransactionHandler.scheduleTransaction() to enqueue a EXECUTE_TRANSACTION
message on the main thread message queue:
public abstract class ClientTransactionHandler {
/** Prepare and schedule transaction for execution. */
void scheduleTransaction(ClientTransaction transaction) {
transaction.preExecute(this);
sendMessage(
ActivityThread.H.EXECUTE_TRANSACTION,
transaction
);
}
}
When EXECUTE_TRANSACTION
is processed it calls TransactionExecutor.execute().
We can now update the initial sequence diagram:
Actual activity launch
TransactionExecutor.execute() calls TransactionExecutor.performLifecycleSequence() which calls back into ActivityThread to create, start and resume the 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;
}
}
}
}
First draw
Let's look at the sequence of calls that leads to the first draw from ActivityThread.handleResumeActivity():
- ActivityThread.handleResumeActivity()
- ➡️ WindowManagerImpl.addView()
- ➡️ WindowManagerGlobal.addView()
- ➡️ ViewRootImpl.setView()
- ➡️ ViewRootImpl.requestLayout()
- ➡️ ViewRootImpl.scheduleTraversals()
- ➡️ Choreographer.postCallback()
- ➡️ Choreographer.scheduleFrameLocked()
Choreographer.scheduleFrameLocked() enqueues a MSG_DO_FRAME
message on the main thread message queue:
When MSG_DO_FRAME
is processed it calls Choreographer.doFrame() which calls ViewRootImpl.doTraversal() which performs a measure pass, a layout pass, and finally the first draw pass on the view hierarchy (see How Android Draws Views):
Conclusion
We started with a high level understanding of what happens once the system creates the app process:
Now we know exactly what happens:
Let's put this together with what we learnt from the prior blog post, and look at everything that happens from when the user taps a launcher icon to when the first activity is drawn and the user can start using the app. You might need to zoom in 🔎 😅:
Now that we have the full picture, we can start digging into how to properly monitor cold start. Stay tuned for more!
Top comments (9)
Isn't "creating the main thread" before "creating the app object" ?
That's how some dependencies start before the app, no?
Absolutely, the main thread is the first thread created so that happens before the Application instance is created. I'm not certain what the Android documentation is trying to convey here, it's likely wrong or maybe to very clear.
So, the diagram doesn't seem right, and that's the first thing I read...
I was trying to show what the doc says vs what's actually happening. I guess I could have added a "please read the words above and below this diagram" header 😉
Oh sorry for that
Can you please show the original of those mistakes?
Maybe you should report it to Google:
issuetracker.google.com/issues
The link is right there in the blog post :) developer.android.com/topic/perfor...
ok
I think I know why I missed it. I use an addon to make all websites in dark, and thus the link text looks exactly like normal one, so I didn't notice there is a link....