Let’s start with an architecture preview:

Framework/Engine/Embedder

Framework

The Framework layer is written in the pure DART language and implements a set of basic features for handling Animation, Painting, and Gestures. Based on Painting, we have encapsulated a set of widgets to smooth out the differences in cross-platform UI presentation and provide a set of widgets to reduce the performance cost of repeated creation and destruction of UI change controls

Embedder layer

What is Embedder? Personal understanding is flutter to run on different platforms will be on different platforms to create container click here for details Such as on Android will create FlutterActivity/FlutterFragment/FlutterView Looking at the source code, you can see that the Embedder layer is mainly responsible for the initialization of the Engine’s thread setup and the initialization of the render component’s setup resource files

Engine layer

The Engine layer is written in C++ and is responsible for the core of flutter initialization, Dart virtual machine management, and the underlying flutter communication and event mechanism

How does ####Embedder relate to Engine Tracing the FlutterEmbedder layer code, it can be found that both the FlutterActivity, FlutterFragment and the container of the final Flutter are FlutterView, and the FlutterView only inherits the SurfaceView

/**
 * An Android view containing a Flutter app.
 */
public class FlutterView extends SurfaceView implements BinaryMessenger, TextureRegistry {
//...
        if (nativeView == null) {
            mNativeView = new FlutterNativeView(activity.getApplicationContext());
        } else{ mNativeView = nativeView; } dartExecutor = mNativeView.getDartExecutor(); flutterRenderer = new FlutterRenderer(mNativeView.getFlutterJNI()); / /... mNativeView.attachViewAndActivity(this, activity); mSurfaceCallback = new SurfaceHolder.Callback() { @Override public void surfaceCreated(SurfaceHolder holder) { assertAttached(); mNativeView.getFlutterJNI().onSurfaceCreated(holder.getSurface()); } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { assertAttached(); mNativeView.getFlutterJNI().onSurfaceChanged(width, height); } @Override public void surfaceDestroyed(SurfaceHolder holder) { assertAttached(); mNativeView.getFlutterJNI().onSurfaceDestroyed(); }}; getHolder().addCallback(mSurfaceCallback); / /... }Copy the code

Nativeview will be notified of surface changes. FlutterNativeView seems to be the decorator of nativeView, so let’s go to FlutterNativeView and have a look:

public FlutterNativeView(@NonNull Context context, boolean isBackgroundView) { //... mFlutterJNI = new FlutterJNI(); / /... Attach (this, isBackgroundView); } FlutterJNIgetFlutterJNI() {
        return mFlutterJNI;
    }

 private void attach(FlutterNativeView view, boolean isBackgroundView) {
        mFlutterJNI.attachToNative(isBackgroundView);
        dartExecutor.onAttachedToJNI();
    }
Copy the code

It can be seen that Navtive associates and initializes Engine #### through FlutterJNI and JNI layers. The code position of Engine associates and initializes attachToNative is here: /flutter/shell/platform/android/platform_view_android_jni.cc

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>(
      FlutterMain::Get().GetSettings(), java_object, is_background_view);
  if (shell_holder->IsValid()) {
    return reinterpret_cast<jlong>(shell_holder.release());
  } else {
    return0; }}Copy the code

Reinterpret_cast is a cast character in C++. The reinterpret_cast() method forces the Pointer to the AndroidShellHolder object to a value of type long and returns it to the Java layer for preservation. Then from AndroidShellHolder analysis: / flutter/shell/platform/android/android_shell_holder. Cc

Step1: create four threads and set the current thread to platform thread
  // The current thread will be used as the platform thread. Ensure that the
  // message loop is initialized.
  fml::MessageLoop::EnsureInitializedForCurrentThread();
  fml::RefPtr<fml::TaskRunner> gpu_runner;
  fml::RefPtr<fml::TaskRunner> ui_runner;
  fml::RefPtr<fml::TaskRunner> io_runner;
  fml::RefPtr<fml::TaskRunner> platform_runner =
      fml::MessageLoop::GetCurrent().GetTaskRunner();
  if (is_background_view) {
    ...
  } else {
    gpu_runner = thread_host_.gpu_thread->GetTaskRunner();
    ui_runner = thread_host_.ui_thread->GetTaskRunner();
    io_runner = thread_host_.io_thread->GetTaskRunner();
  }
  blink::TaskRunners task_runners(thread_label,     // label
                                  platform_runner,  // platform
                                  gpu_runner,       // gpu
                                  ui_runner,        // ui
                                  io_runner         // io
  );
Copy the code
step2:
shell_ =
      Shell::Create(task_runners,             // task runners
                    settings_,                // settings
                    on_create_platform_view,  // platform view create callback
                    on_create_rasterizer      // rasterizer create callback
      );
Copy the code

Flutter/shell/common/shell. Cc if it can be convenient for compiler Engine inconvenient in making direct view

std::unique_ptr<Shell> Shell::Create(
    TaskRunners task_runners,
    Settings settings,
    Shell::CreateCallback<PlatformView> on_create_platform_view,
    Shell::CreateCallback<Rasterizer> on_create_rasterizer) {
  PerformInitializationTasks(settings);
  PersistentCache::SetCacheSkSL(settings.cache_sksl);

  TRACE_EVENT0("flutter"."Shell::Create");

  auto vm = DartVMRef::Create(settings);
  FML_CHECK(vm) << "Must be able to initialize the VM.";

  auto vm_data = vm->GetVMData();

  returnShell::Create(std::move(task_runners), // std::move(settings), // vm_data->GetIsolateSnapshot(), // isolate snapshot DartSnapshot::Empty(), // shared snapshot std::move(on_create_platform_view), // std::move(on_create_rasterizer), // std::move(vm) // ); } / /... std::unique_ptr<Shell> Shell::Create( TaskRunners task_runners, Settings settings, fml::RefPtr<const DartSnapshot> isolate_snapshot, fml::RefPtr<const DartSnapshot> shared_snapshot, Shell::CreateCallback<PlatformView> on_create_platform_view, Shell::CreateCallback<Rasterizer> on_create_rasterizer, DartVMRef vm) { //... shell = CreateShellOnPlatformThread(std::move(vm), std::move(task_runners), // settings, // std::move(isolate_snapshot), // std::move(shared_snapshot), // on_create_platform_view, // on_create_rasterizer // );  / /... }Copy the code

1 according to the initialization setting to perform the Task 2 created DartVM and turn it into a Shell in the Create method Here find Shell real CreateShellOnPlatformThread the Create method

// Create the rasterizer on the GPU thread. // Create the platform view on the platform thread (this thread). // Ask the  platform viewfor the vsync waiter. This will be used by the engine
auto vsync_waiter = platform_view->CreateVSyncWaiter();
// Create the IO manager on the IO thread.
// Create the engine on the UI thread.
Copy the code

According to the code comments: Create a Rasterizer on the GPUThread. 2 Create a PlatformView on the Platform Thread and pass its VSyncWaiter to Engine. 3 Create an Engine on UIThread Then, through the shell Setup method call, unique_PTR platform_view, IO_manager, Rasterizer, and engine are saved to the shell object for the shell object to manage, and the result of the Create shell is returned to Andro The idShellHolder connects the Embedder layer to the Engine layer and initializes four threads, a DartVM and an Engine. Here’s a diagram to summarize:

####Flutter and Threads The Flutter Engine does not create and manage threads itself. The creation and management of the Flutter Engine threads is administered by Embedder

The Embeder provides four Task runners, each responsible for a different Task. The Flutter Engine does not care which thread the Task Runner runs on, but it requires that the thread configuration be stable throughout its lifetime. That is, a Runner should always run on the same thread

Platform Task Runner

When the Engine is initialized, the current Thread is set to the PlatformTaskRunner Thread, which is the bridge between the Flutter and the Native Thread. It is officially required that all interactions with the Flutter must occur in the PlatformThread. Since there are many modules in FlutterEngine that are not thread-safe, it is important to note that the PlatformThread blocking does not directly cause the Flutter application to stall. Because the core of Flutter rendering is in UIThread

UI Task Runner

After Embedder and Engine were associated, Engine started executing the Dart Root ISOLATE code, and the Root ISOLATE was responsible for creating the Layer Tree that managed what to draw on the screen. Therefore, the overload of this thread will directly cause frame lag.

GPU Task Runner

Modules in GPU Task Runner are responsible for translating the information provided by Layer Tree into actual GPU instructions. GPU Task Runner is also responsible for configuration and management of GPU resources required for each frame drawing, including platform Framebuffer creation, Surface lifecycle management, and ensuring that Texture and Buffers are available during drawing. GPU Runner can cause frame scheduling delay of UI Runner, and overload of GPU Runner can cause lag of Flutter application. In general, users don’t have the opportunity to submit tasks directly to GPU Runner because neither the platform nor the Dart code can run into GPU Runner. But Embeder can still submit tasks to GPU Runner. Therefore, it is recommended to create a dedicated GPU Runner thread for each Engine instance.

IO Task Runner

The main function is to read compressed image formats from image storage (such as disk) and process the image data in preparation for GPU Runner rendering.

Conclusion:

Platform Thread GPU Thread UI Thread IO Thread
The interface to the Flutter Engine is called Execute GPU commands Run the Dart root ISOLATE code Read and process image data

isolate

The ISOLates are isolated, each isolate has its own memory and a single threaded entity. The ISOLates do not share memory with each other and have independent GC. The code in the ISOLATE executes sequentially and is single-threaded, so there is no problem with resource contention or variable state synchronization, and locks are not required. Concurrency in Dart is implemented by multiple ISOLates in parallel

The ISOLates do not share memory, so they cannot communicate with each other directly. Instead, they communicate asynchronously through ports

Flutter Engine Runners and Dart Isolate

The Dart Isolate is managed by the Dart VM and cannot be directly accessed by the Flutter Engine. The Root Isolate uses Dart’s C++ call capability to submit UI rendering tasks to UI Runner. This allows interaction with Flutter Engine modules. The TASKS related to the Flutter UI are also submitted to the UI Runner. The Dart Isolate and the Flutter Runner are independent of each other. They cooperate with the Flutter Runner through task scheduling

DalvikVM and DartVM

DartVM memory management

To be exact, DartVM and JVM GC mechanisms are similar in that they use generational collection to divide memory into new generation and old generation:

The new generation

The objects allocated for the first time are all in the new generation. This area is mainly used to store objects with small memory and short life cycle, such as local variables. The new generation will frequently perform memory reclamation (GC), which uses a copy-clean algorithm that divides memory into two pieces and uses one piece at a time while running, while the other is used. When GC occurs, surviving objects in the currently used memory block are copied to the standby memory block, then the currently used memory block is cleared, and finally, the roles of the two memory blocks are swapped.

The old s

Older GC uses mark-clean and memory areas in the thread are left in an immutable state at the time of marking, similar to stop the World in the JVM, but due to dart’s excellent schedule mechanism and the low GC frequency of older GC, this problem rarely occurs.

schedule

In reviewing the Engine source code, we see that dartVM is passed to Engine as a variable. To minimize the performance impact of GC on APP and UI, Engine calls dartVM to trigger GC when FlutterApp is idle/without user interaction/background

isolate

Remember that Thread is the smallest unit of operation performed in the JVM. Dart differs from the JVM in that: The ISOLates have their own stacks. Memory is not shared between the isolates. GC does not affect each other. Asynchronous communication can only be achieved through ports. The entire rendering process of the Flutter Framework widgets runs in one ISOLATE