Cold start of an Android application

Hello everyone! I haven't written anything for a long time.



This will be a series of posts about the process of cold launching an Android application, from the moment you click on the icon to the creation of the application process.



image



General scheme



image



Opening the "window" ...



Before starting a new application process, system_server creates a start window using the PhoneWindowManager .addSplashScreen () method :



public class PhoneWindowManager implements WindowManagerPolicy {

  public StartingSurface addSplashScreen(...) {
    ...
    PhoneWindow win = new PhoneWindow(context);
    win.setIsStartingWindow(true);
    win.setType(TYPE_APPLICATION_STARTING);
    win.setTitle(label);
    win.setDefaultIcon(icon);
    win.setDefaultLogo(logo);
    win.setLayout(MATCH_PARENT, MATCH_PARENT);

    addSplashscreenContent(win, context);

    WindowManager wm = (WindowManager) context.getSystemService(
      WINDOW_SERVICE
    );
    View view = win.getDecorView();
    wm.addView(view, params);
    ...
  }

  private void addSplashscreenContent(PhoneWindow win,
      Context ctx) {
    TypedArray a = ctx.obtainStyledAttributes(R.styleable.Window);
    int resId = a.getResourceId(
      R.styleable.Window_windowSplashscreenContent,
      0
    );
    a.recycle();
    Drawable drawable = ctx.getDrawable(resId);
    View v = new View(ctx);
    v.setBackground(drawable);
    win.setContentView(v);
  }
}


The start window is what the user will see while the application is running. The window will be displayed until the Activity is launched and the first frame is drawn. That is, until the cold start is completed . The user can see this window for a long time, so try to make it pleasant.



image



The content of the start window is taken from the drawable resources windowSplashscreenContent and windowBackground of the launched Activity . A trivial example of such a window:



image



If the user restores the Activity from the Recent screen mode , while clicking on the application icon, then system_servercalls the TaskSnapshotSurface .create () method to create a start window from an already taken screenshot.



Once the start window is shown to the user, system_server is ready to start the application process and calls the ZygoteProcess method. startViaZygote () :



public class ZygoteProcess {
  private Process.ProcessStartResult startViaZygote(...) {
    ArrayList<String> argsForZygote = new ArrayList<>();
    argsForZygote.add("--runtime-args");
    argsForZygote.add("--setuid=" + uid);
    argsForZygote.add("--setgid=" + gid);
    argsForZygote.add("--runtime-flags=" + runtimeFlags);
    ...
    return zygoteSendArgsAndGetResult(openZygoteSocketIfNeeded(abi),
                                          zygotePolicyFlags,
                                          argsForZygote);
  }
}


In the code, you can see that the ZygoteProcess. zygoteSendArgsAndGetResult () sends startup arguments over the socket to the Zygote process .



Zygote's "separation"



According to Android documentation about memory management it follows:

Each application process is started by forking (splitting) from an existing Zygote process ...
I wrote briefly about this in the previous article about launching Android . Now let's take a deeper look at the ongoing processes.



When the system boots, the Zygote process starts and executes the ZygoteInit .main () method :



public class ZygoteInit {

  public static void main(String argv[]) {
    ...
    if (!enableLazyPreload) {
        preload(bootTimingsTraceLog);
    }
    // The select loop returns early in the child process after
    // a fork and loops forever in the zygote.
    caller = zygoteServer.runSelectLoop(abiList);
    // We're in the child process and have exited the
    // select loop. Proceed to execute the command.
    if (caller != null) {
      caller.run();
    }
  }

  static void preload(TimingsTraceLog bootTimingsTraceLog) {
    preloadClasses();
    cacheNonBootClasspathClassLoaders();
    preloadResources();
    nativePreloadAppProcessHALs();
    maybePreloadGraphicsDriver();
    preloadSharedLibraries();
    preloadTextResources();
    WebViewFactory.prepareWebViewInZygote();
    warmUpJcaProviders();
  }
}


As you can see the ZygoteInit method. main () does 2 important things:



  • Loads all the necessary system libraries and resources of the Android framework. Such preloading not only saves memory, but also saves application launch time.
  • Next, it runs the ZygoteServer.runSelectLoop () method, which in turn starts the socket and starts listening for calls to this socket.


When a command to fork the process arrives on the socket, the ZygoteConnection.

processOneCommand () processes arguments using the ZygoteArguments method. parseArgs () and runs the Zygote. forkAndSpecialize () :



public final class Zygote {

  public static int forkAndSpecialize(...) {
    ZygoteHooks.preFork();

    int pid = nativeForkAndSpecialize(...);

    // Set the Java Language thread priority to the default value.
    Thread.currentThread().setPriority(Thread.NORM_PRIORITY);

    ZygoteHooks.postForkCommon();
    return pid;
  }
}


image



Note: Starting with Android 10, there is an optimization feature called Unspecialized App Process , which has a pool of non-specialized Zygote processes to launch applications even faster.



The application has started!



After the fork, the child process runs the RuntimeInit method. commonInit () , which sets the default UncaughtExceptionHandler . Next, the process starts the ActivityThread method. main () :



public final class ActivityThread {

  public static void main(String[] args) {
    Looper.prepareMainLooper();

    ActivityThread thread = new ActivityThread();
    thread.attach(false, startSeq);

    Looper.loop();
  }

  final ApplicationThread mAppThread = new ApplicationThread();

  private void attach(boolean system, long startSeq) {
    if (!system) {
      IActivityManager mgr = ActivityManager.getService();
      mgr.attachApplication(mAppThread, startSeq);
    }
  }
}


Two interesting things happen here:



  • ActivityThread.main() (Thread) Looper.loop(), Looper-. ( MainThread- aka UiThread) () . Looper , MessageQueue.
  • , ActivityThread.attach() IPC- ActivityManagerService.attachApplication() system_server-, , MainThread .


image





In the system_server process, the ActivityManagerService. attachApplication () calls the ActivityManagerService. attachApplicationLocked () , which completes the configuration of the application being launched:



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;
  }
}


A couple of key takeaways:



  • The system_server process makes an IPC request to the ActivityThread method. bindApplication () in our application process, which routes the request to the ActivityThread method. handleBindApplication () in the MainThread application.
  • Immediately after that, system_server schedules the launch of the Pending Activity, Service and BroadcastReciever of our application.
  • ActivityThread. handleBindApplication () loads the APK file and application components.
  • Developers have the ability to slightly influence processes before running the ActivityThread method. handleBindApplication () , so this is where cold start monitoring of the application should start.


image



Let's take a closer look at the third point and find out what and how happens when loading application components and resources. The order of steps is as follows:



  • Loading and instantiating the AppComponentFactory class .
  • Calling the AppComponentFactory. instantiateClassLoader () .
  • Calling the AppComponentFactory. instantiateApplication () to load and instantiate the Application class .
  • For each declared ContentProvider , in order of precedence, a call to the AppComponentFactory. instantiateProvider () to load its class and create an instance, after calling the ContentProvider method. onCreate () .
  • Finally, calling the Application. onCreate () .


Epilogue



We started exploring cold boot from a very general abstracted level:



image



Now we know what's going on under the hood:



image



Well, that was a long post. But that's not all! In the next posts, we will continue our deep dive into the process of launching an Android application. Stay with us!



All Articles