Inicialização a frio de um aplicativo Android

Olá a todos! Faz muito tempo que não escrevo nada.



Esta será uma série de posts sobre o processo de inicialização a frio de um aplicativo Android, desde o momento em que você clica no ícone até a criação do processo do aplicativo.



imagem



Esquema geral



imagem



Abrindo a "janela" ...



Antes de iniciar um novo processo de aplicativo, system_server cria uma janela inicial usando o 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);
  }
}


A janela inicial é o que o usuário verá enquanto o aplicativo está em execução. A janela será exibida até que a Atividade seja iniciada e o primeiro quadro seja desenhado. Isto é, até que a inicialização a frio seja concluída . O usuário pode ver esta janela por um longo tempo, então tente torná-la agradável.



imagem



O conteúdo da janela inicial é obtido dos recursos drawable windowSplashscreenContent e windowBackground da Activity lançada . Um exemplo trivial de tal janela:



imagem



se o usuário restaura a atividade do modo de tela recente , enquanto clica no ícone do aplicativo, então system_serverchama o método TaskSnapshotSurface .create () para criar uma janela inicial a partir de uma captura de tela já tirada.



Uma vez que a janela inicial é mostrada ao usuário, system_server está pronto para iniciar o processo do aplicativo e chama o 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);
  }
}


No código, você pode ver que o ZygoteProcess. zygoteSendArgsAndGetResult () envia argumentos de inicialização através do soquete para o processo Zygote .



"Separação" do zigoto



De acordo com a documentação do Android sobre gerenciamento de memória, segue:

Cada processo de aplicação é iniciado bifurcando (separando) de um processo Zygote existente ...
Escrevi brevemente sobre isso no artigo anterior sobre o lançamento do Android . Agora vamos dar uma olhada mais profunda nos processos em andamento.



Quando o sistema é inicializado, o processo Zygote inicia e executa o 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 você pode ver o método ZygoteInit. main () faz 2 coisas importantes:



  • Carrega todas as bibliotecas e recursos do sistema necessários da estrutura Android. Esse pré-carregamento não apenas economiza memória, mas também economiza tempo de inicialização do aplicativo.
  • Em seguida, ele executa o método ZygoteServer.runSelectLoop (), que por sua vez inicia o soquete e começa a ouvir chamadas para esse soquete.


Quando um comando para bifurcar o processo chega ao soquete, o ZygoteConnection.

processOneCommand () processa argumentos usando o método ZygoteArguments. parseArgs () e executa o 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;
  }
}


imagem



Observação: a partir do Android 10, há um recurso de otimização chamado Unspecialized App Process , que possui um pool de processos Zygote não especializados para iniciar aplicativos ainda mais rápido.



O aplicativo foi iniciado!



Após a bifurcação, o processo filho executa o método RuntimeInit. commonInit () , que define o UncaughtExceptionHandler padrão . Em seguida, o processo inicia o método ActivityThread. 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);
    }
  }
}


Duas coisas interessantes acontecem aqui:



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


imagem





No processo system_server , o ActivityManagerService. attachApplication () chama o ActivityManagerService. attachApplicationLocked () , que completa a configuração do aplicativo que está sendo iniciado:



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


Algumas dicas importantes:



  • O processo system_server faz uma solicitação IPC ao método ActivityThread. bindApplication () em nosso processo de aplicação, que roteia a solicitação para o método ActivityThread. handleBindApplication () no aplicativo MainThread .
  • Imediatamente depois disso, system_server agenda o lançamento da Pending Activity, Service e BroadcastReciever de nosso aplicativo.
  • ActivityThread. handleBindApplication () carrega o arquivo APK e os componentes do aplicativo.
  • Os desenvolvedores têm a capacidade de influenciar ligeiramente os processos antes de executar o método ActivityThread. handleBindApplication () , então é aqui que o monitoramento de inicialização a frio do aplicativo deve começar.


imagem



Vamos dar uma olhada no terceiro ponto e descobrir o que e como acontece ao carregar componentes e recursos do aplicativo. A ordem das etapas é a seguinte:



  • Carregando e instanciando a classe AppComponentFactory .
  • Chamando o AppComponentFactory. instantiateClassLoader () .
  • Chamando o AppComponentFactory. instantiateApplication () para carregar e instanciar a classe Application .
  • Para cada ContentProvider declarado , em ordem de precedência, uma chamada para AppComponentFactory. instantiateProvider () para carregar sua classe e criar uma instância, após chamar o método ContentProvider. onCreate () .
  • Finalmente, chamando o aplicativo. onCreate () .


Epílogo



Começamos a explorar a inicialização a frio a partir de um nível abstrato muito geral:



imagem



Agora sabemos o que está acontecendo nos bastidores:



imagem



Bem, essa foi uma longa postagem. Mas isso não é tudo! Nas próximas postagens, continuaremos nosso mergulho profundo no processo de lançamento de um aplicativo Android. Fique conosco!



All Articles