Démarrage à froid d'une application Android

Bonjour à tous! Je n'ai rien écrit depuis longtemps.



Ce sera une série d'articles sur le processus de lancement à froid d'une application Android, à partir du moment où vous cliquez sur l'icône jusqu'à la création du processus d'application.



image



Schéma général



image



Ouverture de la "fenêtre" ...



Avant de démarrer un nouveau processus d'application, system_server crée une fenêtre de démarrage à l'aide de la méthode 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 fenêtre de démarrage est ce que l'utilisateur verra pendant l'exécution de l'application. La fenêtre sera affichée jusqu'à ce que l' activité soit lancée et que la première image soit dessinée. Autrement dit, jusqu'à ce que le démarrage à froid soit terminé . L'utilisateur peut voir cette fenêtre pendant longtemps, alors essayez de la rendre agréable.



image



Le contenu de la fenêtre de démarrage est extrait des ressources dessinables windowSplashscreenContent et windowBackground de l' activité lancée . Un exemple trivial d'une telle fenêtre:



image



Si l'utilisateur restaure l'activité à partir du mode d' écran Récent , en cliquant sur l'icône de l'application, alors system_serverappelle la méthode TaskSnapshotSurface .create () pour créer une fenêtre de démarrage à partir d'une capture d'écran déjà prise.



Une fois la fenêtre de démarrage affichée à l'utilisateur, system_server est prêt à démarrer le processus d'application et appelle la méthode 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);
  }
}


Dans le code, vous pouvez voir que ZygoteProcess. zygoteSendArgsAndGetResult () envoie des arguments de démarrage via le socket au processus Zygote .



La «séparation» de Zygote



Selon la documentation Android sur la gestion de la mémoire, il suit:

Chaque processus de candidature est démarré en forçant (fractionnement) à partir d'un processus Zygote existant ...
J'ai brièvement écrit à ce sujet dans l'article précédent sur le lancement d'Android . Examinons maintenant de plus près les processus en cours.



Lorsque le système démarre, le processus Zygote démarre et exécute la méthode 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();
  }
}


Comme vous pouvez le voir, la méthode ZygoteInit. main () fait 2 choses importantes:



  • Charge toutes les bibliothèques et ressources système nécessaires du framework Android. Un tel préchargement permet non seulement d'économiser de la mémoire, mais également de gagner du temps au lancement de l'application.
  • Ensuite, il exécute la méthode ZygoteServer.runSelectLoop (), qui à son tour démarre le socket et commence à écouter les appels à ce socket.


Lorsqu'une commande pour bifurquer le processus arrive sur le socket, le ZygoteConnection.

processOneCommand () traite les arguments à l'aide de la méthode ZygoteArguments. parseArgs () et exécute le 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



Remarque: à partir d'Android 10, il existe une fonctionnalité d'optimisation appelée Processus d'application non spécialisé, qui dispose d'un pool de processus Zygote non spécialisés pour lancer des applications encore plus rapidement.



L'application a démarré!



Après le fork, le processus enfant exécute la méthode RuntimeInit. commonInit () , qui définit le UncaughtExceptionHandler par défaut . Ensuite, le processus démarre la méthode 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);
    }
  }
}


Deux choses intéressantes se produisent ici:



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


image





Dans le processus system_server , ActivityManagerService. attachApplication () appelle ActivityManagerService. attachApplicationLocked () , qui termine la configuration de l'application en cours de lancement:



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


Quelques points clés à retenir:



  • Le processus system_server envoie une requête IPC à la méthode ActivityThread. bindApplication () dans notre processus d'application, qui achemine la demande vers la méthode ActivityThread. handleBindApplication () dans l' application MainThread .
  • Immédiatement après cela, system_server planifie le lancement de l'activité en attente, du service et du BroadcastReciever de notre application.
  • ActivityThread. handleBindApplication () charge le fichier APK et les composants de l'application.
  • Les développeurs ont la possibilité d'influencer légèrement les processus avant d'exécuter la méthode ActivityThread. handleBindApplication () , c'est donc ici que doit démarrer la surveillance du démarrage à froid de l'application.


image



Examinons de plus près le 3ème point et voyons ce qui se passe et comment se passe lors du chargement des composants et des ressources de l'application. L'ordre des étapes est le suivant:



  • Chargement et instanciation de la classe AppComponentFactory .
  • Appel de AppComponentFactory. instantiateClassLoader () .
  • Appel de AppComponentFactory. instantiateApplication () pour charger et instancier la classe Application .
  • Pour chaque ContentProvider déclaré , par ordre de priorité, un appel à AppComponentFactory. instantiateProvider () pour charger sa classe et créer une instance, après avoir appelé la méthode ContentProvider. onCreate () .
  • Enfin, appelez l'application. onCreate () .


Épilogue



Nous avons commencé à explorer le démarrage à froid à partir d'un niveau abstrait très général:



image



maintenant nous savons ce qui se passe sous le capot:



image



Eh bien, c'était un long article. Mais ce n'est pas tout! Dans les prochains articles, nous continuerons notre plongée dans le processus de lancement d'une application Android. Rester avec nous!



All Articles