Rendering the first frame of an Android application

image



Hello everyone! This post is a continuation of the post about a deep dive into the process of downloading and launching an Android application. Today we will go a little further and discuss the moment when the main Activity of the application is launched and the system should render the first frame. Please, under the cat.



Following the official documentation, the running application process is responsible for performing the following steps:



  1. Creating an object of the Application class.
  2. Start the main thread (MainThread aka UiThread).
  3. Creation of the starting Activity, which is specified in the manifest.
  4. Expansion (inflating) of views. That is, the creation of views, which are registered in the xml file.
  5. Layout of sizes (View.measure ()) and placement (View.layout ()) of views on the screen.
  6. Performing initial rendering.


After the first frame has been drawn, the system process replaces the displayed background window, replacing it with the Application Activity . The user can now interact with the application.



image



Now let's take a closer look at all the steps.



Start of the main stream



In the previous post, we learned:



  • , ActivityThread.main(), IPC- ActivityManagerService.attachApplication() system_server.
  • system_server IPC- ActivityThread.bindApplication(), BIND_APPLICATION MessageQueue .
  • When the IPC call to ActivityManagerService. attachApplication () completed, ActivityThread. main () calls Looper. loop () , which will loop forever (as long as the application is running) and will process messages coming into the MessageQueue .
  • The first message to be processed is BIND_APPLICATION . At this point, the ActivityThread method will be called. handleBindApplication () , which will load the APK and other application components.


image



An important point: nothing happens in the main thread of the application process until an IPC call to the ActivityManagerService is made. attachApplication () .



Planning the launch of the Activity



Let's see what happens in the system_server process after calling the ActivityThread method. 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 string that is relevant to the launch of the Activity is mAtmInternal. attachApplication (...) . The method calls ActivityTaskManagerService. attachApplication () , which in turn calls the 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 code does the following:



  • Bypasses every display.
  • Gets the stack of focused Activities for this display.
  • Loops through each Activity of the target Activity stack.
  • If the Activity belongs to a running process, then the ActivityStackSupervisor method is called. realStartActivityLocked () . Note that the andResume parameter will be true if the Activity is at the top of the stack.


This is what the ActivityStackSupervisor method looks like. 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 method calls that we have seen occur in the system_server process . ClientLifecycleManager method. scheduleTransaction () makes an IPC call to ActivityThread. scheduleTransaction () in the application process that calls ClientTransactionHandler. scheduleTransaction () to queue the EXECUTE_TRANSACTION message :



public abstract class ClientTransactionHandler {

    /** Prepare and schedule transaction for execution. */
    void scheduleTransaction(ClientTransaction transaction) {
        transaction.preExecute(this);
        sendMessage(
          ActivityThread.H.EXECUTE_TRANSACTION,
          transaction
        );
    }
}


When processing the EXECUTE_TRANSACTION message , the TransactionExecutor method is called. execute () .



Now you can update the diagram:



image



Actual launch of Activity



Method TransactionExecutor. execute () calls TransactionExecutor.

performLifecycleSequence () , which in turn makes a callback in the ActivityThread to create ( create ), start ( start ) and resume ( 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;
      }
    }
  }
}


Updating the diagram:



image



First frame



Let's take a look at the sequence of method calls that lead to the rendering of the first frame:



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


Choreographer method. scheduleFrameLocked () queues the MSG_DO_FRAME message :



image



When processing the MSG_DO_FRAME message , the Choreographer method is called. doFrame () , which in turn calls ViewRootImpl. doTraversal () , which passes the measure pass and layout pass , and finally the first draw pass through the view hierarchy:



image



Conclusion



We started with a high level of understanding of what happens when the system creates the application process:



image



Now we know what exactly happens β€œunder the hood”:



image



Now let's connect the diagrams from the previous post, from the moment the user taps the application icon until the first one is drawn. frame:



image



Now that we have the complete picture, we can begin to figure out how to properly control a cold start. The next post will be about that! See you.



All Articles