Inicio en frío de una aplicación de Android

¡Hola a todos! No he escrito nada en mucho tiempo.



Esta será una serie de publicaciones sobre el proceso de lanzamiento en frío de una aplicación Android, desde el momento en que haces clic en el icono hasta la creación del proceso de la aplicación.



imagen



Esquema general



imagen



Abriendo la "ventana" ...



Antes de iniciar un nuevo proceso de aplicación, system_server crea una ventana de inicio utilizando el método PhoneWindowManager .addSplashScreen () :



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


La ventana de inicio es lo que verá el usuario mientras se ejecuta la aplicación. La ventana se mostrará hasta que se inicie la Actividad y se dibuje el primer marco. Es decir, hasta que se complete el arranque en frío . El usuario puede ver esta ventana durante mucho tiempo, así que intente que sea agradable.



imagen



El contenido de la ventana de inicio se toma de los recursos dibujables windowSplashscreenContent y windowBackground de la actividad iniciada . Un ejemplo trivial de una ventana de este tipo:



imagen



si el usuario restaura la Actividad desde el modo de pantalla Reciente , mientras hace clic en el icono de la aplicación, entonces system_serverllama al método TaskSnapshotSurface .create () para crear una ventana de inicio a partir de una captura de pantalla ya tomada.



Una vez que se muestra la ventana de inicio al usuario, system_server está listo para iniciar el proceso de aplicación y llama al método ZygoteProcess. 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);
  }
}


En el código, puede ver que ZygoteProcess. zygoteSendArgsAndGetResult () envía argumentos de inicio a través del socket al proceso Zygote .



La "separación" del cigoto



Según la documentación de Android sobre la gestión de la memoria, sigue:

Cada proceso de solicitud se inicia bifurcando (dividiendo) de un proceso Zygote existente ...
Escribí brevemente sobre esto en el artículo anterior sobre el lanzamiento de Android . Ahora echemos un vistazo más profundo a los procesos en curso.



Cuando el sistema arranca, el proceso Zygote se inicia y ejecuta el método ZygoteInit .main () :



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


Como puede ver el método ZygoteInit. main () hace 2 cosas importantes:



  • Carga todas las bibliotecas del sistema y los recursos necesarios del marco de Android. Esta precarga no solo ahorra memoria, sino que también ahorra tiempo de inicio de la aplicación.
  • A continuación, ejecuta el método ZygoteServer.runSelectLoop (), que a su vez inicia el socket y comienza a escuchar las llamadas a este socket.


Cuando un comando para bifurcar el proceso llega al socket, ZygoteConnection.

processOneCommand () procesa argumentos utilizando el método ZygoteArguments. parseArgs () y ejecuta el 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;
  }
}


imagen



Nota: A partir de Android 10, hay una función de optimización llamada Proceso de aplicación no especializado, que tiene un grupo de procesos Zygote no especializados para iniciar aplicaciones aún más rápido.



¡La aplicación ha comenzado!



Después de la bifurcación, el proceso hijo ejecuta el método RuntimeInit. commonInit () , que establece el UncaughtExceptionHandler predeterminado . A continuación, el proceso inicia el método ActivityThread. principal () :



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


Aquí pasan dos cosas interesantes:



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


imagen





En el proceso system_server , ActivityManagerService. attachApplication () llama a ActivityManagerService. attachApplicationLocked () , que completa la configuración de la aplicación que se inicia:



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


Un par de conclusiones clave:



  • El proceso system_server realiza una solicitud de IPC al método ActivityThread. bindApplication () en nuestro proceso de aplicación, que enruta la solicitud al método ActivityThread. handleBindApplication () en la aplicación MainThread .
  • Inmediatamente después de eso, system_server programa el lanzamiento de la Actividad pendiente, el Servicio y el BroadcastReciever de nuestra aplicación.
  • ActivityThread. handleBindApplication () carga el archivo APK y los componentes de la aplicación.
  • Los desarrolladores tienen la capacidad de influir ligeramente en los procesos antes de ejecutar el método ActivityThread. handleBindApplication () , por lo que aquí es donde debe comenzar el monitoreo de inicio en frío de la aplicación.


imagen



Echemos un vistazo más de cerca al tercer punto y descubramos qué y cómo sucede cuando se cargan los componentes y recursos de la aplicación. El orden de los pasos es el siguiente:



  • Cargando e instanciando la clase AppComponentFactory .
  • Llamar a AppComponentFactory. instantiateClassLoader () .
  • Llamar a AppComponentFactory. instantiateApplication () para cargar y crear una instancia de la clase Application .
  • Para cada ContentProvider declarado , en orden de precedencia, una llamada a AppComponentFactory. instantiateProvider () para cargar su clase y crear una instancia, después de llamar al método ContentProvider. onCreate () .
  • Finalmente, llamando a Application. onCreate () .


Epílogo



Comenzamos a explorar el arranque en frío desde un nivel abstracto muy general:



imagen



ahora sabemos lo que está pasando bajo el capó:



imagen



Bueno, esa fue una publicación larga. ¡Pero eso no es todo! En las próximas publicaciones, continuaremos nuestra inmersión profunda en el proceso de lanzamiento de una aplicación de Android. ¡Quédate con nosotros!



All Articles