There is no need to talk about how popular flutter is here, almost all big factory apps are used, let’s get started

# introduction

flutterThere are three layers. The first layer is usdartCode, includingUIComponents, animations,GestureWait, so every time we create a newdartFile, needimportClasses in those packages:


The engine layer has a separate repository, Flutter/Engine, on Github, which is responsible for low-level page rendering, native API calls, including CPU, GPU management and scheduling, etc.


Platform specific implementation layer, Flutter implements the corresponding platform (iOS, Android, Windows, Linux) based on the interface agreed with the engine. Common implementation differences related to RunLoop, Thread, and Surface drawing on different platforms are reflected in this layer.

Note that Flutter also uses some third_parties, such as Skia, Google’s cross-platform graphics library, for page rendering. Flutter is called a self-drawing rendering engine because Skia ensures strong consistency of rendering across different platforms

Here’s a question to consider: Which layer do the FlutterActivity Java classes belong to? Is it framework or Engine or Platform? Let us know in the comments





  • It’s easy for you to learn. It’s integrated hereflutterThe knowledge of





# initialization

Here’s android as an example, and iOS as well. Flutter can be divided into two situations. One is a pure Flutter project, a new Flutter project, and the other is integrated into an existing project in the form of modules

  • Pure FLUTTER project: Call flutter create before compilation. Init an Android directory that contains the Manifest, build.gradle, and MainActivity (inherited from FlutterActivity). This is a completely Android project. So when the packaged app runs, the entry class will be mainActivity in the/Android directory

  • For module inheritance, we need to define a custom Activity and let it inherit from FlutterActivity as the entry to flutter. Specify an engine in this path. The page loaded by this engine is set to the Activity via setContent


# Platform layer

Whether it’s a pure flutter app or inherited as a Module, the Activity we show the flutter page inherits from FlutterActivity, so when the Activity starts, It goes to the onCreate method in FlutterActivity, so what’s going on here

  @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();
  }
Copy the code

Let’s break it down one by one:

  • FlutterActivityinheritanceActivityAnd implementedFlutterActivityAndFragmentDelegateThe Host interface in this proxy class and the LifecycleOwner interface that gets the lifecycle can listen for the lifecycle
  • configureWindowForTransparencyIf:FlutterActivityIf background mode is transparent (default is opaque mode), set the entireFlutterActivityWindow transparent and hidden status bar. In fact, if we don’t use it in native projectsFlutterModuleFor mixed development, you don’t need to focus on this approach. Because the default is opaque.
  • super.onCreate: calls the parent classActivityonCreateMethod for the default configuration of the activity
  • FlutterActivityAndFragmentDelegate:flutterFlutterActivityFlutterFragmentThe same functions of flutter are encapsulated in this class, that is, this class contains the main functions of flutter operation
  • onAttach:flutterInitialization entry for the system and engine
  • onActivityCreated: Start executiondartCode to bind the engine toactivity/fragment
  • configureWindowForTransparencyIf:manifestIn the configuration fortranslucent, it will bebackgroundMake it transparent
  • createFlutterView: createflutterView(flutterviewWill hold aflutterSurfaceVieworflutterTextureView)
  • setContentViewWill:flutterviewSet to currentactivityview
  • configureStatusBarForFullscreenFlutterExperience: Sets the status bar to the full-screen mode


# onAttach

OnAttach mainly does various initialization operations, and the code logic is as follows

  void onAttach(@NonNull Context context) {
    ensureAlive();
    if (flutterEngine == null) {
      setupFlutterEngine();
    }
    platformPlugin = host.providePlatformPlugin(host.getActivity(), flutterEngine);

    if(host.shouldAttachEngineToActivity()) { flutterEngine .getActivityControlSurface() .attachToActivity(host.getActivity(),  host.getLifecycle()); } host.configureFlutterEngine(flutterEngine); }Copy the code
  • EnsureAlive: Determines whether the current activity/fragment has been released. References to the activity and fragment are passed in when the delegate is instantiated
  • SetupFlutterEngine: To initialize the flutter engine, first fetch it from memory. If not, fetch it from host (FlutterActivity returns null if it is FlutterFragment, try getActivity). If not, create a new one
  • PlatformPlugin: Encapsulates many Android methods, such as beeping, exiting the current page, retrieving clipboard content, and so on
  • AttachToActivity: try to register the Activity to the FlutterEnginePluginRegistry, FlutterEnginePluginRegistry also called plug-in registry. Many libraries in Our Dependencies reuse native methods. The main logic is implemented with DART, and part of the logic is implemented with native Android /iOS, so we need to write a flutterPlugin and register it with Method Chennel. The attachToActivity method notifies all plug-ins currently connected to The FlutterEngine to attach them to the Activity
  • ConfigureFlutterEngine: Do more with the engine by overriding the method in FlutterActivity/Fragment, such that FlutterActivity registers the flutterRngine to the plugins list in Pubspec.yaml


# createFlutterView

The purpose of FlutterView is to display a Flutter UI on an Android device, drawing content provided by the FlutterEngine. There are four types of flutterview in Android. The first is FlutterSurfaceView or FlutterTextureView, which is used to display our app objects. But in addition to those two there are FlutterSplashView and FlutterImageView

  • The main function of FlutterSplashView is to display a SplashScreen transition diagram before the FlutterView render.
  • FlutterView creation relies on a FlutterTextureView or FlutterSurfaceView, The essence of the judgment condition is to check whether the window background of FlutterActivity is transparent (FlutterFragment is determined by the Arguments flutterview_render_mode parameter). A: It’s so opaque that it’s his texture.
  @NonNull
  View onCreateView(...if (host.getRenderMode() == RenderMode.surface) {
      ...
      flutterView = new FlutterView(host.getActivity(), flutterSurfaceView);
    } else{... flutterView =new FlutterView(host.getActivity(), flutterTextureView);
    }

    flutterView.addOnFirstFrameRenderedListener(flutterUiDisplayListener);

    flutterSplashView = newFlutterSplashView(host.getContext()); . flutterView.attachToFlutterEngine(flutterEngine);return flutterSplashView;
  }
Copy the code
  • AttachToFlutterEngine: This method provides the Surface of the FlutterSurfaceView to the specified FlutterRender to draw the Flutter UI to the current FlutterSurfaceView


# FlutterEngine

FlutterEngine is a standalone Container for Flutter runtime environment that allows Dart code to run in Android applications. The Dart code in FlutterEngine can be executed in the background or rendered to the screen using the accompanying FlutterRenderer and Dart code. Rendering can be started and stopped. This allows FlutterEngine to move from UI interaction to the ability to just do data processing, and then back to UI interaction.

public FlutterEngine(
    @NonNull Context context,
    @NonNull FlutterLoader flutterLoader,
    @NonNull FlutterJNI flutterJNI,
    @NonNull PlatformViewsController platformViewsController,
    @Nullable String[] dartVmArgs,
    boolean automaticallyRegisterPlugins) {
  this.flutterJNI = flutterJNI; flutterLoader.startInitialization(context.getApplicationContext()); . attachToJni();this.dartExecutor = new DartExecutor(flutterJNI, context.getAssets());
  this.dartExecutor.onAttachedToJNI();
  this.renderer = newFlutterRenderer(flutterJNI); . xxxChannel =newXxxChannel(...) ;// Create message channels for passing events and messages. }Copy the code
  • FlutterJNI: The JNI interfaces of the Engine layer are registered and bound here. It allows you to call c/ C ++ code in the Engine layer
  • DartExecutor: used to perform the Dart code (calling DartExecutor executeDartEntrypoint (DartExecutor. DartEntrypoint) can be performed, a FlutterEngine performs a)
  • FlutterRenderer: The FlutterRenderer instance attaches a RenderSurface to the FlutterView

Here, the relationship between FlutterEngine and DartExecutor, Dart VM and Isolate can be roughly summarized as follows:

  • A Native process has only one DartVM.
  • A DartVM(or Native process) can have multiple FlutterEngines.
  • Multiple Flutterengines run on their respective isolates. their memory data is not shared and they communicate through ports (top-level functions) that the ISOLatespreset.
  • FlutterEngine can run code in the background without rendering the UI; You can also render the UI through FlutterRender.
  • When the first FlutterEngine is initialized, DartVM will be created and no other DartVM environment will be created after that.
  • FlutterEngine can manage cache via FlutterEngineCache. It is recommended to use Ali Idle fish’s Flutter_boost to manage projects that mix native and flutter pages.
  • We can manually modify the entry function of a Flutter project, the flutter_assets resource path, and the initial Route of a Flutter project. The API involved is FlutterLoader
  • DartExecutor, FlutterJNI, Host, etc. In a nutshell, it uses BinaryMessager to transfer data and calls DartExecutor’s execution code after modifying the entry function and initializing the Route parameter


# FlutterJNI

The purpose of FlutterJNI is to bridge the Interface between Android Java and Flutter Engine C/C++. In order to facilitate the management of JNI interfaces, all JNI interfaces are encapsulated in FlutterJNI, so that most calls in FlutterJNI are related to specific “Platform Views”, and the number of “platform Views” may be many. So, after the attachToNative method is executed, each FlutterJNI instance holds a local “platform view” ID, which is shared with bendingC/C++ engine code. This ID is passed to all local methods of the specific Platform view.

public class FlutterJNI {...public FlutterJNI(a) {
    // We cache the main looper so that we can ensure calls are made on the main thread
    // without consistently paying the synchronization cost of getMainLooper().mainLooper = Looper.getMainLooper(); }...@UiThread
  public void attachToNative(boolean isBackgroundView) {
    ensureRunningOnMainThread();
    ensureNotAttachedToNative();
    nativePlatformViewId = nativeAttach(this, isBackgroundView);
  }

  private native long nativeAttach(@NonNull FlutterJNI flutterJNI, boolean isBackgroundView); . }Copy the code

Platform_view_android_jni. Cc, platform_view_android_jni. This registers the C/C ++ layer implementation of the call in FlutterJNI

bool RegisterApi(JNIEnv* env) {
  static const JNINativeMethod flutter_jni_methods[] = {
      {
          .name = "nativeAttach",
          .signature = "(Lio/flutter/embedding/engine/FlutterJNI; Z)J",
          .fnPtr = reinterpret_cast<void*>(&AttachJNI),
      },
      ...
  };
  if (env->RegisterNatives(g_flutter_jni_class->obj(), flutter_jni_methods,
                           fml::size(flutter_jni_methods)) ! =0) {
    FML_LOG(ERROR) << "Failed to RegisterNatives with FlutterJNI";
    return false; }... }static jlong AttachJNI(JNIEnv* env, jclass clazz, jobject flutterJNI, jboolean is_background_view) {
  fml::jni::JavaObjectWeakGlobalRef java_object(env, flutterJNI);
  auto shell_holder = std::make_unique<AndroidShellHolder>( / / [6]
      FlutterMain::Get().GetSettings(), java_object, is_background_view);
  if (shell_holder->IsValid()) {
    return reinterpret_cast<jlong>(shell_holder.release());
  } else {
    return 0; }}Copy the code
  • AndroidShellHolder: This is a C/C++ Shell holding class. The engine of Flutter uses core technologies, Skia, a 2D graphics rendering library, Dart, a VM for object-oriented language for garbage collection, and hosts them in a Shell. Different platforms have different shells


# FlutterJNIAndroidShellHolder

AndroidShellHolder contains Flutter Settings parameters, Java references to FlutterJNI, PlatformViewAndroid objects (which will be created later), Shell objects, etc.

AndroidShellHolder::AndroidShellHolder(
    flutter::Settings settings,
    fml::jni::JavaObjectWeakGlobalRef java_object,
    bool is_background_view)
    : settings_(std::move(settings)), java_object_(java_object) {
  ...
  // Create three threads: UI thread, GPU thread, IO threadthread_host_ = {thread_label, ThreadHost::Type::UI | ThreadHost::Type::GPU | ThreadHost::Type::IO}; . fml::WeakPtr<PlatformViewAndroid> weak_platform_view; Shell::CreateCallback<PlatformView> on_create_platform_view = [is_background_view, java_object, &weak_platform_view](Shell& shell) { std::unique_ptr<PlatformViewAndroid> platform_view_android; . platform_view_android = std::make_unique<PlatformViewAndroid>(/ / [7]
            shell,                   // delegate
            shell.GetTaskRunners(),  // task runners
            java_object,             // java object handle for JNI interop
            shell.GetSettings()
                .enable_software_rendering  // use software rendering
        ); 
        weak_platform_view = platform_view_android->GetWeakPtr(a);returnplatform_view_android; }; ./ / [8]
  shell_ =
      Shell::Create(task_runners,             // task runners
                    GetDefaultWindowData(),   // window data
                    settings_,                // settings
                    on_create_platform_view,  // platform view create callback
                    on_create_rasterizer      // rasterizer create callback); platform_view_ = weak_platform_view; . }Copy the code
  • The creation of PlatformViewAndroid:, which is responsible for managing the platform side is where event processing is executed in the UI thread
  • Shell: Load third-party libraries and create Java virtual machines


Shell layer initialization

The Shell is the “central nervous system” of the Flutter application, consisting of multiple components that inherit their corresponding Delegate classes.

std::unique_ptr<Shell> Shell::Create(
    TaskRunners task_runners,
    const WindowData window_data,
    Settings settings,
    Shell::CreateCallback<PlatformView> on_create_platform_view,
    Shell::CreateCallback<Rasterizer> on_create_rasterizer) {...auto vm = DartVMRef::Create(settings); // Create the Dart VM
  auto vm_data = vm->GetVMData(a);return Shell::Create(std::move(task_runners),        //
                       std::move(window_data),         //
                       std::move(settings),            //
                       vm_data->GetIsolateSnapshot(),  // isolate snapshot
                       on_create_platform_view,        //
                       on_create_rasterizer,           //
                       std::move(vm)                   //
  );
}
Copy the code

Create the Shell in the platform thread, then create the Rasterizer in the rasterized thread, PlatformView in the Platform thread, ShellIOManager in the IO thread, Engine in the UI thread, And set these four elements into the Shell. Each Shell inherits its Delegate, and each Delegate passes events to the Shell through its Delegate.

  • Rasterizer: Rasterization of graphics
  • PlatformView: A widget that can be embedded into native View on Android and iOS. With viewId lookup, the underlying flutter engine draws and renders. The main application of FLUTTER is widgets (Native views that are not easy to implement), such as WebViews, video players, maps, etc.
  • ShellIOManager: Create OpenGL Context (a cross-language, cross-platform application programming interface (API) for rendering 2D and 3D vector graphics. This interface consists of nearly 350 different function calls to draw everything from simple graphics bits to complex 3d scenes.


# PlatformView

  • To enable some existing Native controls to be directly incorporated into the Flutter app, the Flutter team provides two widgets, AndroidView and UIKitView
  • Platform View is a general name for AndroidView and UIKitView. It allows the Native View to be embedded into the flutter widget system to control the Native View by Datr code
void PlatformViewAndroid::NotifyCreated( fml::RefPtr
       
         native_window)
        {
  if (android_surface_) {
    InstallFirstFrameCallback(a); . fml::TaskRunner::RunNowOrPostTask(
        task_runners_.GetRasterTaskRunner(),
        [&latch, surface = android_surface_.get(),
         native_window = std::move(native_window)]() {
          surface->SetNativeWindow(native_window); . }); . } PlatformView::NotifyCreated(a); }void PlatformView::NotifyCreated(a) {
  std::unique_ptr<Surface> surface;
  auto* platform_view = this; . fml::TaskRunner::RunNowOrPostTask(
      task_runners_.GetRasterTaskRunner(), [platform_view, &surface, &latch]() {
        surface = platform_view->CreateRenderingSurface(a); . }); . delegate_.OnPlatformViewCreated(std::move(surface));
}
Copy the code
  • PlatformViewAndroid performs View drawing and event handling on the JNI layer, registers SurfaceView to Flutter Eingine, The ANative_window class is called to bridge FlutterUI and AndroidUI by providing the engine with the canvas to draw on
  • The main implementation is to set native_window to the surface and notify the surface to the delegate (i.e. Shell). In other words, PlatformView is primarily a bridge between the Surface and the Shell


# Rasterizer

We know that the main job of gpu thread is to Raster the Layer Tree and then send it to GPU, among which the core method ScopedFrame::Raster(), This is where the Rasterizer comes in. The Rasterizer holds a drawing Surface that is currently active and displayed on the screen. The Rasterizer draws the Layer Tree submitted from the Engine on this Surface

Rasterizer::Rasterizer(Delegate& delegate, TaskRunners task_runners)
    : Rasterizer(delegate,
                 std::move(task_runners),
                 std::make_unique<flutter::CompositorContext>(
                     delegate.GetFrameBudget{} ()))...void Rasterizer::Draw( fml::RefPtr
       <:pipeline>
        > pipeline)
       <:layertree> {
  TRACE_EVENT0("flutter"."GPURasterizer::Draw");

  flutter::Pipeline<flow::LayerTree>::Consumer consumer =
      std::bind(&Rasterizer::DoDraw, this, std::placeholders::_1);

  // Consuming pipeline tasks [see Section 2.3]
  switch (pipeline->Consume(consumer)) {
    case flutter::PipelineConsumeResult::MoreAvailable: {
      task_runners_.GetGPUTaskRunner() - >PostTask(
          [weak_this = weak_factory_.GetWeakPtr(), pipeline]() {
            if (weak_this) {
              weak_this->Draw(pipeline); }});break;
    }
    default:
      break; }}Copy the code
  • After the execution of the Consume () returns a value for the PipelineConsumeResult: : MoreAvailable, explain and tasks need to be processed, is again different LayerTree of the Draw () process


# ShellIOManager

ShellIOManager, GrContext, and SkiaUnrefQueue are all created in the IO thread

class ShellIOManager final : public IOManager {
  ...
  void NotifyResourceContextAvailable(sk_sp<GrContext> resource_context);
  void UpdateResourceContext(sk_sp<GrContext> resource_context); .fml::WeakPtr<GrContext> GetResourceContext(a) const override;
  fml::RefPtr<flutter::SkiaUnrefQueue> GetSkiaUnrefQueue(a) const override;
}

Copy the code
  • ShellIOManager inherits from the IOManager class. IOManager is the interface class that manages methods to get GrContext resources and Skia queues. Both of these are graphics-related and will be analyzed in future articles
  • NotifyResourceContextAvailable and UpdateResourceContext method is a way to create and access to inform GrContext





# Flutter start!

Initialization is complete and everything is ready except the East Wind gate. Let’s see how the Flutter starts!

# FlutterActivity

When we create a Flutter_app, a Kotlin class is generated.

class MainActivity: FlutterActivity() {}Copy the code

This is not implemented by default, so it will go directly to FlutterActivity, and our startup process will mainly start in onStart

@Override
protected void onStart(a) {
  super.onStart();
  lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_START);
  delegate.onStart();
}
Copy the code

Here you can see, through the lifecycle set life cycle to ON_START first, and then call the FlutterActivityAndFragmentDelegate onStart method

# FlutterActivityAndFragmentDelegate

There are two main methods, ensureAlive, that WE talked about earlier, and we’re going to focus on doInitialFlutterViewRun

void onStart(a) { ensureAlive(); doInitialFlutterViewRun(); }...private void doInitialFlutterViewRun(a) {...if (flutterEngine.getDartExecutor().isExecutingDart()) {
    // No warning is logged because this situation will happen on every config
    // change if the developer does not choose to retain the Fragment instance.
    // So this is expected behavior in many cases.
    return; }...// Configure the Dart entrypoint and execute it.
  DartExecutor.DartEntrypoint entrypoint =
      new DartExecutor.DartEntrypoint(
          host.getAppBundlePath(), host.getDartEntrypointFunctionName());
  flutterEngine.getDartExecutor().executeDartEntrypoint(entrypoint); / / [9]
}
Copy the code

Reloading is not supported in FlutterView or restarting dart. Determine whether the dart code is currently executed

  • FlutterActivity is then used to retrieve the package path and Dart entry method, and DartEntrypoint is created. DartEntrypoint is then used to execute Dart code
  • The Executor’s executeDartEntrypoint method is then called through flutterEngine

# DartExecutor

The front, as we have said, was founded in FlutterEngine DartExecutor when created, and in DartExecutor. OnAttachedToJNI method, set the DartMessager into FlutterJNI, Here we focus on the executeDartEntrypoint method

public void executeDartEntrypoint(@NonNull DartEntrypoint dartEntrypoint) {... flutterJNI.runBundleAndSnapshotFromLibrary( dartEntrypoint.pathToBundle, dartEntrypoint.dartEntrypointFunctionName,null, assetManager); . }Copy the code
  • We found that DartExecutor continued to call FlutterJNI runBundleAndSnapshotFromLibrary method

# FlutterJNI

Let’s take a look at runBundleAndSnapshotFromLibrary method

  @UiThread
  public void runBundleAndSnapshotFromLibrary( @NonNull String bundlePath, @Nullable String entrypointFunctionName, @Nullable String  @NonNull AssetManager assetManager) {
    ensureRunningOnMainThread();
    ensureAttachedToNative();
    nativeRunBundleAndSnapshotFromLibrary(
        nativeShellHolderId,
        bundlePath,
        entrypointFunctionName,
        pathToEntrypointFunction,
        assetManager);
  }
Copy the code
  • NativeRunBundleAndSnapshotFromLibrary this is a native method
  private native void nativeRunBundleAndSnapshotFromLibrary(
      long nativeShellHolderId,
      @NonNull String bundlePath,
      @Nullable String entrypointFunctionName,
      @Nullable String pathToEntrypointFunction,
      @NonNull AssetManager manager);
Copy the code
  • Here the Launch method of AndroidShellHolder (CPP class) is called via JNI

# AndroidShellHolder

The Launch method does a bunch of configuration and eventually calls the Shell’s RunEngine method

void AndroidShellHolder::Launch(std::shared_ptr<AssetManager> asset_manager,
                                const std::string& entrypoint,
                                const std::string& libraryUrl) {... shell_->RunEngine(std::move(config.value()));
}
Copy the code

Enter the core Shell layer

# Shell

It’s worth noting that, as we saw earlier, the Engine is created and run in the UI thread. So the Dart code executed by Engine here needs to be executed in the UI thread. Let’s first look at what the RunEngine method does:

void Shell::RunEngine(
    RunConfiguration run_configuration,
    const std::function<void(Engine::RunStatus)>& result_callback) {... fml::TaskRunner::RunNowOrPostTask(
      task_runners_.GetUITaskRunner(), / / [10]
      fml::MakeCopyable(
          [run_configuration = std::move(run_configuration),
           weak_engine = weak_engine_, result]() mutable{...auto run_result = weak_engine->Run(std::move(run_configuration)); .result(run_result);
          }));
}
Copy the code
  • Again, this goes into Engine’s Run method

# Engine

Let’s take a look at what the Run method does:

// ./shell/common/engine.cc
Engine::RunStatus Engine::Run(RunConfiguration configuration) {... last_entry_point_ = configuration.GetEntrypoint(a); last_entry_point_library_ = configuration.GetEntrypointLibrary(a);auto isolate_launch_status =
      PrepareAndLaunchIsolate(std::move(configuration)); / / [11]. std::shared_ptr<DartIsolate> isolate = runtime_controller_->GetRootIsolate().lock(a);bool isolate_running =
      isolate && isolate->GetPhase() == DartIsolate::Phase::Running;

  if (isolate_running) {
    ...
    std::string service_id = isolate->GetServiceId(a); fml::RefPtr<PlatformMessage> service_id_message = fml::MakeRefCounted<flutter::PlatformMessage>( kIsolateChannel,// Set this to IsolateChannel
            std::vector<uint8_t>(service_id.begin(), service_id.end()),
            nullptr);
    HandlePlatformMessage(service_id_message); / / [12]
  }
  return isolate_running ? Engine::RunStatus::Success
                         : Engine::RunStatus::Failure;
}
Copy the code
  • PrepareAndLaunchIsolate: This method is used to start the Isolate, and our DART code also runs in this method
  • HandlePlatformMssage: This passes the DartIsolate state to the Platform layer for processing
  • Here we’re going to look at how the dart code starts, so we’re going to look at the PrepareAndLaunchIsolate method, okay
Engine::RunStatus Engine::PrepareAndLaunchIsolate( RunConfiguration configuration) {
  UpdateAssetManager(configuration.GetAssetManager());
  auto isolate_configuration = configuration.TakeIsolateConfiguration(a); std::shared_ptr<DartIsolate> isolate = runtime_controller_->GetRootIsolate().lock(a); .if(! isolate_configuration->PrepareIsolate(*isolate)) {
    return RunStatus::Failure;
  }
  if (configuration.GetEntrypointLibrary().empty()) { // The library passed in before is empty, enter the branch
    if(! isolate->Run(configuration.GetEntrypoint(),
                      settings_.dart_entrypoint_args)) {
      returnRunStatus::Failure; }}else {
    if(! isolate->RunFromLibrary(configuration.GetEntrypointLibrary(),
                                 configuration.GetEntrypoint(),
                                 settings_.dart_entrypoint_args)) {
      returnRunStatus::Failure; }}}Copy the code
  • After a series of decisions, dart_ISOLATE’s Run method is entered

# DartIsolate

  • This brings us to the final step, where the Dart code is actually executed
[[nodiscard]] bool DartIsolate::Run(const std::string& entrypoint_name,
                                    const std::vector<std::string>& args,
                                    const fml::closure& on_run) {
  if(phase_ ! = Phase::Ready) {return false;
  }

  tonic::DartState::Scope scope(this);

  auto user_entrypoint_function =
      Dart_GetField(Dart_RootLibrary(), tonic::ToDart(entrypoint_name.c_str()));

  auto entrypoint_args = tonic::ToDart(args);

  if (!InvokeMainEntrypoint(user_entrypoint_function, entrypoint_args)) {
    return false;
  }

  phase_ = Phase::Running;

  if (on_run) {
    on_run(a); }return true;
}
Copy the code
  • This first sets the STATUS of the Isolate to Running and then calls dart’s main method. Here InvokeMainEntrypoint is the key to executing the main method
[[nodiscard]] static bool InvokeMainEntrypoint( Dart_Handle user_entrypoint_function, Dart_Handle args) {... Dart_Handle start_main_isolate_function = tonic::DartInvokeField(Dart_LookupLibrary(tonic::ToDart("dart:isolate")),
                             "_getStartMainIsolateFunction"{}); .if (tonic::LogIfError(tonic::DartInvokeField(
          Dart_LookupLibrary(tonic::ToDart("dart:ui")), "_runMainZoned",
          {start_main_isolate_function, user_entrypoint_function, args}))) {
    return false;
  }
  return true;
}
Copy the code

That’s it, and the Dart code is up and running!

# Four threads?

Front FlutterJNIAndroidShellHolder we see that it created out of the three threads UI, gpu, IO in the process of running behind, such as grating, and they are repeatedly used, so what are they and what role? The Flutter engine itself does not create or manage its own threads. Thread control is the responsibility of the Embedder layer. The engine requires that Embedder provide four Task Runner references, but doesn’t care if each runner’s thread is independent. But we only saw three up there? There is also a platform thread, which refers to the main thread of our platform. Take Android as an example, it is our main thread.

Note: Each engine has a separate UI, GPU, and IO Runnter thread; All sharing engines share Platform Runner’s threads

# Platform Task Runner

The Platform Task Runner (or Platform Thread) is responsible for processing Platform (Android /iOS) messages. As a simple example, our MethodChannel callback, onMethodCall, is on this thread

  • However, there are some differences between them. Generally, a Flutter application starts with an Engine instance, which creates a thread for Platform Runner to use
  • However, blocking the Platform Thread does not directly cause the Flutter application to stall. However, it is not recommended to perform heavy operations on the primary Runner because the Platform Thread may be forcibly killed by the system’s Watchdog

# UI Task Runner

UI Task Runner is used to execute Root Isolate code, which runs on the thread of the platform corresponding to the thread and is a child thread. At the same time, the Root isolate binds many of the Flutter functions required by the engine to render.

  • When a new frame arrives, the Engine notifies the Flutter Engine via Root Isolate that a frame needs to be rendered. Upon receiving the notification from the Flutter Engine, the platform creates objects and components and generates a Layer Tree. The generated Layer Tree is then submitted to the Flutter Engine. At this point, only the content that needs to be drawn is generated and no screen rendering is performed, and the Root Isolate is responsible for drawing the Layer Tree created onto the screen, so if the thread is overloaded, frames will get stuck
  • Note here that blocking this thread directly causes the Flutter application to stall and drop frames. Therefore, in practice, it is recommended to create a dedicated GPU Runner thread for each Engine instance or create other ISOLates. The isolates created do not bind to the function of Flutter, but can only perform data calculation and cannot invoke the function of Flutter. And the life cycle of the created ISOLates is controlled by the Root isolates, the Root isolates stop, the other isolates stop, and the threads that the created isolates run on are provided by the thread pool in DartVM

# GPU Task Runner

GPU Task Runner Is used to execute GPU commands. The Layer Tree is created in UI Task Runner, and the information provided by the Layer Tree is converted into GPU instructions that can be executed by the platform on GPU Task Runner. In addition to converting the information provided by Layer Tree into GPU instructions that can be executed by the platform, GPU Task Runner is also responsible for managing GPU resources required for each frame drawing, including the creation of the platform Framebuffer, Surface life cycle management, And the drawing timing of Texture and Buffers, etc

  • In general, UI Runner and GPU Runner run on different threads. GPU Runner will request the data of the next frame to UI Runner according to the progress of the current frame execution, and the delayed task of UI Runner may appear when the task is heavy. However, the advantage of this scheduling mechanism is to ensure that GPU Runner does not become overloaded, and at the same time avoid unnecessary resource consumption of UI Runner
  • GPU Runner can cause frame scheduling delay of UI Runner, and overload of GPU Runner can cause lag of Flutter application. Therefore, it is recommended to create a dedicated GPU Task Runner thread for each Engine instance in practical use

# IO Task Runner

IO Task Runner also runs in the sub-thread corresponding to the platform, and its main role is to do some pre-processed read operations in preparation for GPU Runner’s rendering operations. We can think of IO Task Runner as the assistant of GPU Task Runner, which can reduce the extra work of GPU Task Runner. For example, in preparation of Texture, IO Runner first reads compressed image binary data, decompresses it into a format that the GPU can process, and then passes the data to the GPU for rendering.

  • IO Task Runner does not block the Flutter, although there may be a delay in loading images and resources, it is recommended to open a separate thread for IO Task Runner.

# epilogue

Answer: FlutterActivity, FlutterVC and other business classes embedded in the native layer of Song platform belong to platform layer. Did you get that right? To create a flutter architecture, native developers need to look at platform-engine-framework from the bottom up, while a Flutter developer needs to look at framework-engine-platform from the top down

  • Github summarizes the knowledge of Flutter for everyone to learn