冷たいスタート水へのダイビング🥶


Header image: A Song of Ice and Fire by Romain Guy.


このブログシリーズは、安定性とパフォーマンスの監視のAndroidアプリの生産に焦点を当てています.先週、2009年の測定時間について書きました
次のブログ記事の上で、コールドスタートを監視する方法を探ります.によるとApp startup time documentation :

A cold start refers to an app's starting from scratch: the system's process has not, until this start, created the app's process. Cold starts happen in cases such as your app's being launched for the first time since the device booted, or since the system killed the app.

At the beginning of a cold start, the system has 3 tasks:

  1. Loading and launching the app.
  2. Displaying a starting window.
  3. Creating the app process.

このポストは、アプリケーションのプロセスの作成にランチャーアイコンをタップからコールドスタートの冒頭に深いダイビングです.

Diagram created with WebSequenceDiagram.


アクティビティ.startactivit ()



ユーザーがランチャーアイコンをタップすると、ランチャーアプリケーションのプロセスコールActivity.startActivity() , どちらがInstrumentation.execStartActivity() :
public class Instrumentation {

  public ActivityResult execStartActivity(...) {
    ...
    ActivityTaskManager.getService()
        .startActivity(...);
  }
}
ランチャーアプリのプロセスはIPC コールするActivityTaskManagerService.startActivity()system_server プロセス.The system_server プロセスはほとんどのシステムサービスをホストする.

スタートウィンドウを見つめる👀


新しいアプリケーションプロセスを作成する前にsystem_server プロセスは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);
  }
}
スタートウィンドウは、アプリケーションプロセスが起動している間にユーザーが見ているものです.ユーザーは、長い間、起動ウィンドウを見つめているので、それが良いように確認してください😎.

起動したウィンドウの内容は、起動されたアクティビティーのwindowSplashscreenContent and windowBackground ドロドロ.詳しくはチェックアウトするAndroid App Launching Made Gorgeous .

を返します.Recents screen 代わりに、ランチャーのアイコンをタップsystem_server プロセスコールTaskSnapshotSurface.create() アクティビティーの保存されたスナップショットを描画する開始ウィンドウを作成するには.
スタートウィンドウが表示されるとsystem_server プロセスは、アプリケーションのプロセスと呼び出しを開始する準備ができて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);
  }
}
ZygoteProcess.zygoteSendArgsAndGetResult() ソケット上の開始引数をZygoteプロセスに送信します.

フォーキングZygote🍴


Androidドキュメントによるとmemory management :

Each app process is forked from an existing process called Zygote. The Zygote process starts when the system boots and loads common framework code and resources (such as activity themes). To start a new app process, the system forks the Zygote process then loads and runs the app's code in the new process. This approach allows most of the RAM pages allocated for framework code and resources to be shared across all app processes.


システムが起動すると、Zygoteプロセスが起動して起動する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();
  }
}
ご覧の通り.ZygoteInit.main() 2つの重要なことを行います.
  • これはAndroidのフレームワークのクラス&リソース、共有ライブラリ、グラフィックドライバなどをプリロードします.
  • それから、それは呼び出しますZygoteServer.runSelectLoop() はソケットをオープンし、待ちます.
  • そのソケットにforkingコマンドを受信すると、ZygoteConnection.processOneCommand() 引数をZygoteArguments.parseArgs() 呼び出し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;
      }
    }
    

    注意:Android 10では、最適化されていないアプリケーションプロセスと呼ばれる最適化のサポートが追加されました.USAP ), 特注の待ち行列の群れ.余分なメモリのコストでわずかに高速起動します.アンドロイド搭載IORap これは非常に良い結果を与える.

    アプリが生まれる✨


    一度fork、子アプリのプロセスを実行しますRuntimeInit.commonInit()
    インストールするdefault UncaughtExceptionHandler . その後、アプリケーションのプロセスを実行します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);
        }
      }
    }
    
    ここに2つの興味深い部分があります.

  • ActivityThread.main() 呼び出しLooper.loop() どれが永遠にループし、新しいメッセージを待っているかMessageQueue .

  • ActivityThread.attach() IPCの呼び出しをActivityManagerService.attachApplication()system_server プロセスは、アプリケーションのメインスレッドが行く準備ができていることをお知らせします🚀.

  • アプリPuppeering


    system_server プロセスActivityManagerService.attachApplication() 呼び出しActivityManagerService.attachApplicationLocked() アプリケーションの設定を終了します.
    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 system_server プロセスはIPCコールをActivityThread.bindApplication() アプリのプロセスでは、呼び出しをスケジュールActivityThread.handleBindApplication() アプリケーションのメインスレッドで.
  • 直後にsystem_server プロセスは、任意の保留中の活動、サービス、および放送受信機の開始をスケジュールします.

  • ActivityThread.handleBindApplication() APKを読み込み、次の順序でアプリケーションコンポーネントを読み込みます.
  • アプリを読み込むAppComponentFactory サブクラスでインスタンスを作成します.
  • 呼び出しAppComponentFactory.instantiateClassLoader() .
  • 呼び出しAppComponentFactory.instantiateApplication() アプリをロードするにはApplication サブクラスでインスタンスを作成します.
  • 毎宣言ContentProvider , インpriority order , 呼び出しAppComponentFactory.instantiateProvider() クラスを読み込み、インスタンスを作成するにはContentProvider.onCreate() .
  • 呼び出しApplication.onCreate() .
  • Appの開発者は前に費やした時間にほとんど影響を与えているActivityThread.handleBindApplication() , だから、アプリコールドスタート監視を開始する必要があります.

  • 初期の初期設定


    できるだけ早くコードを実行する必要がある場合は、いくつかのオプションがあります.
  • 最も早いフックはAppComponentFactory クラスが読み込まれます.
  • 追加するappComponentFactory attribute アプリケーションタグにAndroidManifest.xml .
  • あなたがAndroidXを使うならば、あなたは加える必要がありますtools:replace="android:appComponentFactory" を呼び出し、AndroidX AppComponentFactory
  • 静的初期化子を追加し、タイムスタンプを保存するようなことができます.
  • ダウンサイド:これはAndroidのP +でのみ利用可能であり、コンテキストへのアクセスを持っていません.
  • アプリケーション開発者のための安全な早期フックですApplication.onCreate() .
  • ライブラリ開発者のための安全な早期フックContentProvider.onCreate() . このトリックは、2007年にダグスティーブンソンによって大衆化されましたHow does Firebase initialize on Android?
  • 新しいアンドロイドがあるApp Startup library これは、同じプロバイダのトリックに依存します.目標は、多くの代わりに宣言された1つのプロバイダを持つことです.なぜなら、それぞれの宣言されたプロバイダは、数ミリ秒でアプリケーションの起動を遅くし、パッケージマネージャからApplicationInfoオブジェクトのサイズを増加させるからです.
  • 結論


    私たちは、コールドスタートがどのように始まるのかを理解し始めました.

    さて、何が起こるかを正確に知っています.

    ユーザー起動画面のユーザーエクスペリエンスは、ユーザーが画面に触れるが、アプリの開発者は、前に費やした時間にほとんど影響を与えてActivityThread.handleBindApplication() , だから、アプリコールドスタート監視を開始する必要があります.
    それは長いポストでした、そして、我々はコールドスタートでされることから遠いです.より多くのためにチューニング滞在!