Flutter-Android-Embeder 시작 프로세스

개술
Flutter의 시작은 Embedder, Engine, Framework 세 부분을 포함하고 본고는 안드로이드 플랫폼의 Embdder 모듈의 시작 절차만 설명한다.Flutter는 보통 FlutterActivity를 시작하고 순수한 Flutter Application을 통해 시작합니다.본문 이후의 상황을 예로 삼아 분석하다.
FlutterApplication#onCreate
// io/flutter/app/FlutterApplication.java

  @Override
  @CallSuper
  public void onCreate() {
    super.onCreate();
    FlutterInjector.instance().flutterLoader().startInitialization(this);
  }

실제는 호출FlutterLoader#startInitialization으로 초기화됩니다.
FlutterLoader#startInitialization

  public void startInitialization(@NonNull Context applicationContext) {
    startInitialization(applicationContext, new Settings());
  }

  public void startInitialization(@NonNull Context applicationContext, @NonNull Settings settings) {
    if (this.settings != null) {
      return;
    }
    if (Looper.myLooper() != Looper.getMainLooper()) {
      throw new IllegalStateException("startInitialization must be called on the main thread");
    }

    final Context appContext = applicationContext.getApplicationContext();

    this.settings = settings;

    initStartTimestampMillis = SystemClock.uptimeMillis();
    flutterApplicationInfo = ApplicationInfoLoader.load(appContext);
    VsyncWaiter.getInstance((WindowManager) appContext.getSystemService(Context.WINDOW_SERVICE))
        .init();

    // Use a background thread for initialization tasks that require disk access.
    Callable initTask =
        new Callable() {
          @Override
          public InitResult call() {
            ResourceExtractor resourceExtractor = initResources(appContext);

            if (FlutterInjector.instance().shouldLoadNative()) {
              System.loadLibrary("flutter");
            }

            // Prefetch the default font manager as soon as possible on a background thread.
            // It helps to reduce time cost of engine setup that blocks the platform thread.
            Executors.newSingleThreadExecutor()
                .execute(
                    new Runnable() {
                      @Override
                      public void run() {
                        FlutterJNI.nativePrefetchDefaultFontManager();
                      }
                    });

            if (resourceExtractor != null) {
              resourceExtractor.waitForCompletion();
            }

            return new InitResult(
                PathUtils.getFilesDir(appContext),
                PathUtils.getCacheDirectory(appContext),
                PathUtils.getDataDirectory(appContext));
          }
        };
    initResultFuture = Executors.newSingleThreadExecutor().submit(initTask);
  }

주로 다음과 같은 몇 가지 일을 했다.
  • 주 스레드 확인
  • 등록 Vsync 신호 감청
  • 비동기식 작업을 시작하고 다음을 수행합니다
  • .
  • flutter engine
  • 로드
  • 비동기 초기화 글꼴 관리자
  • asset 디렉터리에 자원 불러오기

  • 이상은 Application이 실행하는 작업입니다. 이어서 보통 FlutterActivity에 들어가면 순서대로 리셋을 볼 수 있습니다.
    FlutterActivity#onCreate
    // io/flutter/embedding/android/FlutterActivity.java
    
      @Override
      protected void onCreate(@Nullable Bundle savedInstanceState) {
        switchLaunchThemeForNormalTheme();
    
        super.onCreate(savedInstanceState);
    
        lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_CREATE);
    
        delegate = new FlutterActivityAndFragmentDelegate(this);
        delegate.onAttach(this);
        delegate.onActivityCreated(savedInstanceState);
    
        configureWindowForTransparency();
        setContentView(createFlutterView());
        configureStatusBarForFullscreenFlutterExperience();
      }
    

    주로 다음과 같은 몇 가지 일을 했다.
  • Splash에서 실제 보기
  • 로 전환
  • delegate를 만들고 대응하는 리셋을 터치합니다
  • 구성 관련 창 속성
  • Activity의ContentView
  • 로 새 FlutterView 만들기
    FlutterActivityAndFragmentDelegate#onAttach
      void onAttach(@NonNull Context context) {
        ensureAlive();
        if (flutterEngine == null) {
          setupFlutterEngine();
        }
    
        if (host.shouldAttachEngineToActivity()) {
          Log.v(TAG, "Attaching FlutterEngine to the Activity that owns this delegate.");
          flutterEngine.getActivityControlSurface().attachToActivity(this, host.getLifecycle());
        }
    
        platformPlugin = host.providePlatformPlugin(host.getActivity(), flutterEngine);
    
        host.configureFlutterEngine(flutterEngine);
      }
    

    주로 다음과 같은 몇 가지 일을 했다.
  • FlutterEngine 설정, 일반적으로 새로 만들기
  • Engine과 용기Activity를 연결
  • 새 PlatformPlugin
  • engine 초기화 완료, 대응 리셋
  • 트리거
    그 중에서 FlutterActivityAndFragmentDelegate#setupFlutterEngine 행위는 비교적 복잡하다
      /* package */ void setupFlutterEngine() {
        Log.v(TAG, "Setting up FlutterEngine.");
    
        // First, check if the host wants to use a cached FlutterEngine.
        String cachedEngineId = host.getCachedEngineId();
        if (cachedEngineId != null) {
          flutterEngine = FlutterEngineCache.getInstance().get(cachedEngineId);
          isFlutterEngineFromHost = true;
          if (flutterEngine == null) {
            throw new IllegalStateException(
                "The requested cached FlutterEngine did not exist in the FlutterEngineCache: '"
                    + cachedEngineId
                    + "'");
          }
          return;
        }
    
        // Second, defer to subclasses for a custom FlutterEngine.
        flutterEngine = host.provideFlutterEngine(host.getContext());
        if (flutterEngine != null) {
          isFlutterEngineFromHost = true;
          return;
        }
    
        Log.v(
            TAG,
            "No preferred FlutterEngine was provided. Creating a new FlutterEngine for"
                + " this FlutterFragment.");
        flutterEngine =
            new FlutterEngine(
                host.getContext(),
                host.getFlutterShellArgs().toArray(),
                /*automaticallyRegisterPlugins=*/ false,
                /*willProvideRestorationData=*/ host.shouldRestoreAndSaveState());
        isFlutterEngineFromHost = false;
      }
    

    여기서는 일반적으로 세 번째 시나리오로 Engine을 새로 만듭니다.
      public FlutterEngine(
          @NonNull Context context,
          @Nullable FlutterLoader flutterLoader,
          @NonNull FlutterJNI flutterJNI,
          @NonNull PlatformViewsController platformViewsController,
          @Nullable String[] dartVmArgs,
          boolean automaticallyRegisterPlugins,
          boolean waitForRestorationData) {
        AssetManager assetManager;
        try {
          assetManager = context.createPackageContext(context.getPackageName(), 0).getAssets();
        } catch (NameNotFoundException e) {
          assetManager = context.getAssets();
        }
        this.dartExecutor = new DartExecutor(flutterJNI, assetManager);
        this.dartExecutor.onAttachedToJNI();
    
        accessibilityChannel = new AccessibilityChannel(dartExecutor, flutterJNI);
        keyEventChannel = new KeyEventChannel(dartExecutor);
        lifecycleChannel = new LifecycleChannel(dartExecutor);
        localizationChannel = new LocalizationChannel(dartExecutor);
        mouseCursorChannel = new MouseCursorChannel(dartExecutor);
        navigationChannel = new NavigationChannel(dartExecutor);
        platformChannel = new PlatformChannel(dartExecutor);
        restorationChannel = new RestorationChannel(dartExecutor, waitForRestorationData);
        settingsChannel = new SettingsChannel(dartExecutor);
        systemChannel = new SystemChannel(dartExecutor);
        textInputChannel = new TextInputChannel(dartExecutor);
    
        this.localizationPlugin = new LocalizationPlugin(context, localizationChannel);
    
        this.flutterJNI = flutterJNI;
        if (flutterLoader == null) {
          flutterLoader = FlutterInjector.instance().flutterLoader();
        }
        flutterLoader.startInitialization(context.getApplicationContext());
        flutterLoader.ensureInitializationComplete(context, dartVmArgs);
    
        flutterJNI.addEngineLifecycleListener(engineLifecycleListener);
        flutterJNI.setPlatformViewsController(platformViewsController);
        flutterJNI.setLocalizationPlugin(localizationPlugin);
        attachToJni();
    
        // TODO(mattcarroll): FlutterRenderer is temporally coupled to attach(). Remove that coupling if
        // possible.
        this.renderer = new FlutterRenderer(flutterJNI);
    
        this.platformViewsController = platformViewsController;
        this.platformViewsController.onAttachedToJNI();
    
        this.pluginRegistry =
            new FlutterEngineConnectionRegistry(context.getApplicationContext(), this, flutterLoader);
    
        if (automaticallyRegisterPlugins) {
          registerPlugins();
        }
      }
    

    주로 다음과 같은 몇 가지 일을 했다.
  • 새 Dart 운영 환경DartExecutor
  • Flutter와 안드로이드 통신을 위한 다양한 채널 대상 만들기
  • engine 초기화 완료 확인(Native 측의 초기화 방법을 호출하여 Flutter engine의 시작 논리를 실행함)
  • 플러그인 등록
  • flutterLoader.startInitialization 이전에 호출되었기 때문에 이곳은 바로 돌아올 것입니다.FlutterLoader#ensureInitializationCompleteengine의 마지막 준비를 책임진다.
    
      public void ensureInitializationComplete(
          @NonNull Context applicationContext, @Nullable String[] args) {
        if (initialized) {
          return;
        }
        if (Looper.myLooper() != Looper.getMainLooper()) {
          throw new IllegalStateException(
              "ensureInitializationComplete must be called on the main thread");
        }
        if (settings == null) {
          throw new IllegalStateException(
              "ensureInitializationComplete must be called after startInitialization");
        }
        try {
          InitResult result = initResultFuture.get();
    
          List shellArgs = new ArrayList<>();
          shellArgs.add("--icu-symbol-prefix=_binary_icudtl_dat");
    
          shellArgs.add(
              "--icu-native-lib-path="
                  + flutterApplicationInfo.nativeLibraryDir
                  + File.separator
                  + DEFAULT_LIBRARY);
          if (args != null) {
            Collections.addAll(shellArgs, args);
          }
    
          String kernelPath = null;
          if (BuildConfig.DEBUG || BuildConfig.JIT_RELEASE) {
            String snapshotAssetPath =
                result.dataDirPath + File.separator + flutterApplicationInfo.flutterAssetsDir;
            kernelPath = snapshotAssetPath + File.separator + DEFAULT_KERNEL_BLOB;
            shellArgs.add("--" + SNAPSHOT_ASSET_PATH_KEY + "=" + snapshotAssetPath);
            shellArgs.add("--" + VM_SNAPSHOT_DATA_KEY + "=" + flutterApplicationInfo.vmSnapshotData);
            shellArgs.add(
                "--" + ISOLATE_SNAPSHOT_DATA_KEY + "=" + flutterApplicationInfo.isolateSnapshotData);
          } else {
            shellArgs.add(
                "--" + AOT_SHARED_LIBRARY_NAME + "=" + flutterApplicationInfo.aotSharedLibraryName);
    
            // Most devices can load the AOT shared library based on the library name
            // with no directory path.  Provide a fully qualified path to the library
            // as a workaround for devices where that fails.
            shellArgs.add(
                "--"
                    + AOT_SHARED_LIBRARY_NAME
                    + "="
                    + flutterApplicationInfo.nativeLibraryDir
                    + File.separator
                    + flutterApplicationInfo.aotSharedLibraryName);
          }
    
          shellArgs.add("--cache-dir-path=" + result.engineCachesPath);
          if (!flutterApplicationInfo.clearTextPermitted) {
            shellArgs.add("--disallow-insecure-connections");
          }
          if (flutterApplicationInfo.domainNetworkPolicy != null) {
            shellArgs.add("--domain-network-policy=" + flutterApplicationInfo.domainNetworkPolicy);
          }
          if (settings.getLogTag() != null) {
            shellArgs.add("--log-tag=" + settings.getLogTag());
          }
    
          long initTimeMillis = SystemClock.uptimeMillis() - initStartTimestampMillis;
    
          if (FlutterInjector.instance().shouldLoadNative()) {
            FlutterJNI.nativeInit(
                applicationContext,
                shellArgs.toArray(new String[0]),
                kernelPath,
                result.appStoragePath,
                result.engineCachesPath,
                initTimeMillis);
          }
    
          initialized = true;
        } catch (Exception e) {
          Log.e(TAG, "Flutter initialization failed.", e);
          throw new RuntimeException(e);
        }
      }
    

    주로 각종 매개 변수를 연결하고 최종적으로native 방법을 호출하여engine가 자신의 초기화 논리를 실행하도록 한다.
    이로써 FlutterActivityAndFragmentDelegate#onAttach의 논리는 마침내 끝났다.
    FlutterActivityAndFragmentDelegate#onActivityCreated
      void onActivityCreated(@Nullable Bundle bundle) {
        Log.v(TAG, "onActivityCreated. Giving framework and plugins an opportunity to restore state.");
        ensureAlive();
    
        Bundle pluginState = null;
        byte[] frameworkState = null;
        if (bundle != null) {
          pluginState = bundle.getBundle(PLUGINS_RESTORATION_BUNDLE_KEY);
          frameworkState = bundle.getByteArray(FRAMEWORK_RESTORATION_BUNDLE_KEY);
        }
    
        if (host.shouldRestoreAndSaveState()) {
          flutterEngine.getRestorationChannel().setRestorationData(frameworkState);
        }
    
        if (host.shouldAttachEngineToActivity()) {
          flutterEngine.getActivityControlSurface().onRestoreInstanceState(pluginState);
        }
      }
    

    상태 회복과 관련되어 일반적으로 주목할 필요가 없다.
    FlutterActivity#createFlutterView
      @NonNull
      private View createFlutterView() {
        return delegate.onCreateView(
            null /* inflater */, null /* container */, null /* savedInstanceState */);
      }
    

    계속:
      View onCreateView(
          LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        Log.v(TAG, "Creating FlutterView.");
        ensureAlive();
    
        if (host.getRenderMode() == RenderMode.surface) {
          FlutterSurfaceView flutterSurfaceView =
              new FlutterSurfaceView(
                  host.getActivity(), host.getTransparencyMode() == TransparencyMode.transparent);
    
          host.onFlutterSurfaceViewCreated(flutterSurfaceView);
    
          flutterView = new FlutterView(host.getActivity(), flutterSurfaceView);
        } else {
          FlutterTextureView flutterTextureView = new FlutterTextureView(host.getActivity());
    
          host.onFlutterTextureViewCreated(flutterTextureView);
    
          flutterView = new FlutterView(host.getActivity(), flutterTextureView);
        }
    
        flutterView.addOnFirstFrameRenderedListener(flutterUiDisplayListener);
    
        flutterSplashView = new FlutterSplashView(host.getContext());
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
          flutterSplashView.setId(View.generateViewId());
        } else {
          flutterSplashView.setId(486947586);
        }
        flutterSplashView.displayFlutterViewWithSplash(flutterView, host.provideSplashScreen());
    
        Log.v(TAG, "Attaching FlutterEngine to FlutterView.");
        flutterView.attachToFlutterEngine(flutterEngine);
    
        return flutterSplashView;
      }
    

    이 뷰는 모델에 따라 다양한 종류의 FlutterView를 만들 것입니다. 이 뷰는 Flutter의 UI를 안드로이드에 표시합니다.
    또한 시작 단계인 플래시 스크린FlutterSplashView이 생성됩니다.
    마지막으로FlutterView#attachToFlutterEngine FlutterView와 FlutterEngine을 연결합니다.
      public void attachToFlutterEngine(@NonNull FlutterEngine flutterEngine) {
        Log.v(TAG, "Attaching to a FlutterEngine: " + flutterEngine);
        if (isAttachedToFlutterEngine()) {
          if (flutterEngine == this.flutterEngine) {
            Log.v(TAG, "Already attached to this engine. Doing nothing.");
            return;
          }
          Log.v(
              TAG,
              "Currently attached to a different engine. Detaching and then attaching"
                  + " to new engine.");
          detachFromFlutterEngine();
        }
    
        this.flutterEngine = flutterEngine;
    
        FlutterRenderer flutterRenderer = this.flutterEngine.getRenderer();
        isFlutterUiDisplayed = flutterRenderer.isDisplayingFlutterUi();
        renderSurface.attachToRenderer(flutterRenderer);
        flutterRenderer.addIsDisplayingFlutterUiListener(flutterUiDisplayListener);
    
        // in a way that Flutter understands.
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
          mouseCursorPlugin = new MouseCursorPlugin(this, this.flutterEngine.getMouseCursorChannel());
        }
        textInputPlugin =
            new TextInputPlugin(
                this,
                this.flutterEngine.getTextInputChannel(),
                this.flutterEngine.getPlatformViewsController());
        localizationPlugin = this.flutterEngine.getLocalizationPlugin();
        androidKeyProcessor =
            new AndroidKeyProcessor(this, this.flutterEngine.getKeyEventChannel(), textInputPlugin);
        androidTouchProcessor =
            new AndroidTouchProcessor(this.flutterEngine.getRenderer(), /*trackMotionEvents=*/ false);
        accessibilityBridge =
            new AccessibilityBridge(
                this,
                flutterEngine.getAccessibilityChannel(),
                (AccessibilityManager) getContext().getSystemService(Context.ACCESSIBILITY_SERVICE),
                getContext().getContentResolver(),
                this.flutterEngine.getPlatformViewsController());
        accessibilityBridge.setOnAccessibilityChangeListener(onAccessibilityChangeListener);
        resetWillNotDraw(
            accessibilityBridge.isAccessibilityEnabled(),
            accessibilityBridge.isTouchExplorationEnabled());
    
        this.flutterEngine.getPlatformViewsController().attachAccessibilityBridge(accessibilityBridge);
        this.flutterEngine
            .getPlatformViewsController()
            .attachToFlutterRenderer(this.flutterEngine.getRenderer());
    
        textInputPlugin.getInputMethodManager().restartInput(this);
    
        // Push View and Context related information from Android to Flutter.
        sendUserSettingsToFlutter();
        localizationPlugin.sendLocalesToFlutter(getResources().getConfiguration());
        sendViewportMetricsToFlutter();
    
        flutterEngine.getPlatformViewsController().attachToView(this);
    
        // Notify engine attachment listeners of the attachment.
        for (FlutterEngineAttachmentListener listener : flutterEngineAttachmentListeners) {
          listener.onFlutterEngineAttachedToFlutterView(flutterEngine);
        }
    
        if (isFlutterUiDisplayed) {
          flutterUiDisplayListener.onFlutterUiDisplayed();
        }
      }
    

    여기서는 두 객체가 연관된 필드를 중심으로 필요한 일부 Plugin을 초기화합니다.
    FlutterActivity#onStart
      @Override
      protected void onStart() {
        super.onStart();
        lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_START);
        delegate.onStart();
      }
    

    Delegate의 핵심 논리:
      void onStart() {
        Log.v(TAG, "onStart()");
        ensureAlive();
        doInitialFlutterViewRun();
      }
    
    
      private void doInitialFlutterViewRun() {
        if (host.getCachedEngineId() != null) {
          return;
        }
    
        if (flutterEngine.getDartExecutor().isExecutingDart()) {
          return;
        }
        String initialRoute = host.getInitialRoute();
        if (initialRoute == null) {
          initialRoute = getInitialRouteFromIntent(host.getActivity().getIntent());
        }
        Log.v(
            TAG,
            "Executing Dart entrypoint: "
                + host.getDartEntrypointFunctionName()
                + ", and sending initial route: "
                + initialRoute);
    
        if (initialRoute != null) {
          flutterEngine.getNavigationChannel().setInitialRoute(initialRoute);
        }
    
        String appBundlePathOverride = host.getAppBundlePath();
        if (appBundlePathOverride == null || appBundlePathOverride.isEmpty()) {
          appBundlePathOverride = FlutterInjector.instance().flutterLoader().findAppBundlePath();
        }
    
        DartExecutor.DartEntrypoint entrypoint =
            new DartExecutor.DartEntrypoint(
                appBundlePathOverride, host.getDartEntrypointFunctionName());
        flutterEngine.getDartExecutor().executeDartEntrypoint(entrypoint);
      }
    

    핵심 행위는dart 가상기를 시작하는 것임을 알 수 있다.
    FlutterActivity#onResume
      @Override
      protected void onResume() {
        super.onResume();
        lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_RESUME);
        delegate.onResume();
      }
    
    

    delegate 코드는 다음과 같습니다.
    // io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java
      void onResume() {
        Log.v(TAG, "onResume()");
        ensureAlive();
        flutterEngine.getLifecycleChannel().appIsResumed();
      }
    

    Flutter에 알림을 보냈습니다. 숙주인Activity가onResume 상태입니다.
    이상은 Android Embedder 레이어의 시작 프로세스입니다.

    좋은 웹페이지 즐겨찾기