Android Vitals - 콜드 부팅 수역 잠입🥶

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


이 블로그 시리즈는 Android 응용 프로그램이 생산에서의 안정성과 성능 모니터링을 주목한다.지난주에 나는 시간 측정에 관한 글을 썼다
다음 블로그 글에서 나는 냉각 가동을 어떻게 감시하는지 연구할 것이다.기준 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.


활용단어참조별의 촉각 ()



사용자가 이니시에이터 아이콘을 클릭하면 이니시에이터 응용 프로그램 프로세스가 Activity.startActivity()을 호출하여 Instrumentation.execStartActivity()에 의뢰합니다.
public class Instrumentation {

  public ActivityResult execStartActivity(...) {
    ...
    ActivityTaskManager.getService()
        .startActivity(...);
  }
}
그리고 이니시에이터 응용 프로그램 프로세스는 system_server 프로세스에서 IPCActivityTaskManagerService.startActivity() 호출을 보냅니다.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);
  }
}
시작 창은 응용 프로그램 프로세스가 시작될 때 사용자가 볼 수 있는 창으로, 활동을 만들고 첫 번째 프레임을 그릴 때까지, 즉 콜드 시작이 끝날 때까지.사용자가 부팅 창을 주시하는 시간이 길어질 수 있으므로 보기에 좋을지 확인하십시오😎.

시작 창의 내용은 활동이 시작된 windowSplashscreenContentwindowBackground에서 그림을 그릴 수 있습니다.자세한 내용은 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()은 플러그인을 통해 시작 파라미터를 수정란 프로세스로 보냅니다.

갈라진 합자🍴


memory management의 Android 문서에 따라 다음을 수행합니다.

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.


시스템이 부트되면 수정란 프로세스가 시작되고 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()은 두 가지 중요한 일을 했습니다.
  • 은 Android 프레임워크 클래스와 자원, 공유 라이브러리, 그래픽 드라이버 등을 미리 불러옵니다. 이 미리 불러오는 것은 메모리를 절약할 뿐만 아니라 시작 시간도 단축합니다.
  • 그리고 ZygoteServer.runSelectLoop()을 호출합니다. 후자는 플러그인을 열고 기다립니다.
  • 이 플러그인에서 지름 명령을 받았을 때 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)라고 불리는 최적화에 대한 지원을 추가했습니다. 이것은 전문화를 기다리는 갈래 합자 탱크입니다.부팅 속도가 조금 빠르지만 추가 메모리가 필요합니다(기본적으로 꺼짐).안드로이드 11에는 IORap이 추가되어 더욱 효과가 좋다.

    응용 프로그램이 탄생했다✨


    갈라진 후 하위 응용 프로그램 프로세스가 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);
        }
      }
    }
    
    여기에는 두 가지 재미있는 부분이 있다.

  • ActivityThread.main() 호출 Looper.loop(), 계속 순환, 새로운 소식이 MessageQueue까지 발표되기를 기다립니다.

  • ActivityThread.attach()system_server 프로세스에서 ActivityManagerService.attachApplication()에 IPC 호출을 보내서 응용 프로그램의 주 루틴이 준비되었음을 알립니다🚀.

  • 응용 프로그램 인형 제작

    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;
      }
    }
    
    몇 가지 주요 사항:
  • system_server 프로세스는 응용 프로그램 프로세스에서 ActivityThread.bindApplication()에 대해 IPC 호출을 하고 이 프로세스는 응용 프로그램 메인 라인에서 ActivityThread.handleBindApplication()에 대한 호출을 조정합니다.
  • 이후 system_server 프로세스는 중단된 활동, 서비스, 방송 수신기의 시작을 즉시 스케줄링합니다.

  • ActivityThread.handleBindApplication() APK 및 앱 구성 요소를 다음 순서로 로드합니다.
  • 응용 프로그램 AppComponentFactory 하위 클래스를 불러오고 실례를 만듭니다.
  • AppComponentFactory.instantiateClassLoader()으로 전화하세요.
  • 에서 AppComponentFactory.instantiateApplication()을 호출하여 응용 프로그램 Application 하위 클래스를 불러오고 실례를 만듭니다.
  • 은 모든 성명의 ContentProvider에 대해 priority order에서 AppComponentFactory.instantiateProvider()을 호출하여 그 종류를 불러오고 실례를 만든 다음에 ContentProvider.onCreate()을 호출합니다.
  • Application.onCreate()으로 전화하세요.
  • 응용 프로그램 개발자는 ActivityThread.handleBindApplication()에 소요된 시간에 거의 영향을 주지 않기 때문에 응용 프로그램 냉각 가동 모니터링은 여기서부터 시작해야 한다.

  • 초기 초기화


    가능한 한 빨리 코드를 실행해야 한다면 다음과 같은 몇 가지 옵션이 있습니다.
  • 의 최초의 갈고리는 AppComponentFactory류를 탑재할 때이다.
  • appComponentFactory attributeAndroidManifest.xml의 응용 프로그램 표시에 추가합니다.
  • AndroidX를 사용하려면 tools:replace="android:appComponentFactory"을 추가하고 AndroidX AppComponentFactory에 호출을 의뢰해야 합니다
  • 에서 정적 초기값 설정 항목을 추가하고 시간 스탬프를 저장하는 등의 작업을 수행할 수 있습니다.
  • 단점: 이것은 Android P+에서만 사용할 수 있으며 상하문에 접근할 수 없습니다.
  • 은 응용 개발자에게 안전한 초기 연결고리는 Application.onCreate()이다.
  • 은 라이브러리 개발자에게 안전한 초기 연결고리는 ContentProvider.onCreate()이다.Doug Stevenson은 How does Firebase initialize on Android?년에 이 기교를 보급하였다
  • 에는 같은 공급자 기교에 의존하는 새로운 AndroidX App Startup library이 있습니다.목표는 한 개의 공급자만 설명하는 것이지 여러 개의 공급자만 설명하는 것이다. 왜냐하면 모든 공급자는 응용 프로그램의 시작 속도를 몇 밀리초 늦추고 패키지 관리자의 응용 프로그램 정보 대상의 크기를 늘리기 때문이다.
  • 결론


    우리는 냉각 가동이 어떻게 시작되는지에 대한 높은 이해부터 시작한다.

    지금 우리는 무슨 일이 일어났는지 정확히 알고 있다.

    사용자가 화면을 터치할 때 활동을 시작하는 사용자 체험이 시작되지만 응용 프로그램 개발자는 ActivityThread.handleBindApplication()까지 걸린 시간에 거의 영향을 주지 않기 때문에 응용 프로그램 냉가동 모니터링은 여기서부터 시작해야 한다.
    이것은 매우 긴 댓글로 우리는 아직 냉각 가동을 완성하지 못했다.더 많은 관심 부탁드립니다!

    좋은 웹페이지 즐겨찾기