preface

You may have seen the architecture of Flutter at the beginning of your study of Flutter as follows

We know that the application layer code for Flutter is written by Dart. The Framework layer provides a set of widgets and other apis. How the Dart code is executed on a particular platform starts with the startup process of Flutter. The problem was solved.

As you can see from the architecture diagram, Embedder is implemented by a specific platform, which is essentially the mid-tier code that ports the Flutter to various platforms. Embedder layer is the key to the Embedder layer startup. After the application startup, the platform native module will perform a series of operations by calling the API of this layer, such as setting up the rendering layer system, creating relevant threads, etc. The most important one is to initialize the Flutter Engine through Embedder layer. DartVM is created in Engine, initializations of various service protocols, initializations of Platform Channels, and so on, and the dart entry method main method is executed in DartVM. At this point, the Flutter module starts successfully.

Flutter start analysis

Android platform code analysis

Android platform layer corresponding Embedder code in engine source code/flutter/shell/platform/android/directory.

Let’s analyze the demo of the Flutter project created by the flutter create my_app command.

Prepare flutter file resources and load libraries

First, my_app will execute FlutterApplication first. Let’s take a look at the implementation of the lifecycle method onCreate in this class

  • /flutter/shell/platform/android/io/flutter/app/FlutterApplication.java
@Override
@CallSuper
public void onCreate() {
    super.onCreate();
    FlutterMain.startInitialization(this);
}
Copy the code

The FlutterMain class is the code for the Embedder layer and its startInitialization method is implemented as follows

  • /flutter/shell/platform/android/io/flutter/view/FlutterMain.java
public static void startInitialization(Context applicationContext) {
    startInitialization(applicationContext, new Settings());
}

public static void startInitialization(Context applicationContext, Settings settings) {
    if(Looper.myLooper() ! = Looper.getMainLooper()) { throw new IllegalStateException("startInitialization must be called on the main thread");
    }
    // Do not run startInitialization more than once.
    if(sSettings ! = null) {return;
    }

    sSettings = settings;

    long initStartTimestampMillis = SystemClock.uptimeMillis();
    initConfig(applicationContext);
    initAot(applicationContext);
    initResources(applicationContext);
    System.loadLibrary("flutter");

    long initTimeMillis = SystemClock.uptimeMillis() - initStartTimestampMillis;
    nativeRecordStartTimestamp(initTimeMillis);
}
Copy the code

The startInitialization method is executed in the main thread. This method mainly initializes configuration information, variables in AOT mode (Debug in JIT mode), and resource files (mainly copy asset resource files to a private folder). Finally, the time taken for the flutter initialization is transferred to the c++ layer through JNI method for recording.

  • /flutter/shell/platform/android/flutter_main.cc
static void RecordStartTimestamp(JNIEnv* env,
                                 jclass jcaller,
                                 jlong initTimeMillis) {
  int64_t initTimeMicros =
      static_cast<int64_t>(initTimeMillis) * static_cast<int64_t>(1000);
  blink::engine_main_enter_ts = Dart_TimelineGetMicros() - initTimeMicros;
}
Copy the code

UML class diagrams for key Java classes

The flutter runtime environment is initialized

FlutterApplication executes the onCreate method and then executes the lifecycle method that starts the page MainActivity. Since MainActivity inherits FlutterActivity and overloads the onCreate method of its parent class, the onCreate method of MainActivity does not use setContentView to set the view to display. So let’s look at the onCreate method of FlutterActivity

  • /flutter/shell/platform/android/io/flutter/app/FlutterActivity.java
private final FlutterActivityDelegate delegate = new FlutterActivityDelegate(this, this);
private final FlutterActivityEvents eventDelegate = delegate;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    eventDelegate.onCreate(savedInstanceState);
}
Copy the code

FlutterActivity inherits from Activity, so the view setting is presumably in FlutterActivityDelegate’s onCreate method. Let’s take a look at its implementation

  • /flutter/shell/platform/android/io/flutter/app/FlutterActivityDelegate.java
@Override
public void onCreate(Bundle savedInstanceState) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
        Window window = activity.getWindow();
        window.addFlags(LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
        window.setStatusBarColor(0x40000000);
        window.getDecorView().setSystemUiVisibility(PlatformPlugin.DEFAULT_SYSTEM_UI);
    }

    String[] args = getArgsFromIntent(activity.getIntent());
    FlutterMain.ensureInitializationComplete(activity.getApplicationContext(), args);

    flutterView = viewFactory.createFlutterView(activity);
    if (flutterView == null) {
        FlutterNativeView nativeView = viewFactory.createFlutterNativeView();
        flutterView = new FlutterView(activity, null, nativeView);
        flutterView.setLayoutParams(matchParent);
        activity.setContentView(flutterView);
        launchView = createLaunchView();
        if (launchView != null) {
            addLaunchView();
        }
    }

    if (loadIntent(activity.getIntent())) {
        return;
    }

    String appBundlePath = FlutterMain.findAppBundlePath(activity.getApplicationContext());
    if (appBundlePath != null) {
        runBundle(appBundlePath);
    }
}
Copy the code

We find a sentence in the code activity.setContentView(flutterView); Set the display view for the current MainActivity. How did the flutterView be created?

  1. First, set the immersive status bar according to the current system version;
  2. Gets the parameter information passed in with the intent when the Activity is started.
  3. Perform FlutterMain ensureInitializationComplete method;
  4. Create FlutterNativeView;
  5. Create FlutterView according to FlutterNativeView;
  6. Set FlutterView to the activity’s content view;
  7. Use FlutterMain to find the path of the appBundle and execute the appBundle.

Let’s analysis steps 3 FlutterMain ensureInitializationComplete method, first look at the specific implementation

  • /flutter/shell/platform/android/io/flutter/view/FlutterMain.java
public static void ensureInitializationComplete(Context applicationContext, String[] args) {
    if(Looper.myLooper() ! = Looper.getMainLooper()) { throw new IllegalStateException("ensureInitializationComplete must be called on the main thread"); }...if (sInitialized) {
        return;
    }
    try {
        sResourceExtractor.waitForCompletion();

        List<String> shellArgs = new ArrayList<>();
        shellArgs.add("--icu-data-file-path="+ sIcuDataPath); . String appBundlePath = findAppBundlePath(applicationContext); String appStoragePath = PathUtils.getFilesDir(applicationContext); String engineCachesPath = PathUtils.getCacheDirectory(applicationContext); nativeInit(applicationContext, shellArgs.toArray(new String[0]), appBundlePath, appStoragePath, engineCachesPath); sInitialized =true;
    } catch (Exception e) {
        Log.e(TAG, "Flutter initialization failed.", e); throw new RuntimeException(e); }}Copy the code

We found that this method also requires that it be executed on the main thread, only once, and once it is executed, it is indicated by the sInitialized variable that it will not be executed again. Try-catch block of code in the first sentence sResourceExtractor. WaitForCompletion () to wait for the initialization of resources after the initialization will perform down, otherwise you will have been blocked. This initializes the configuration parameters, the appBundle path, application storage directory, engine cache directory, and other information that flutter contains. This information is then initialized at the c++ layer by calling JNI methods. The corresponding c++ methods of the JNI methods are as follows

  • /flutter/shell/platform/android/flutter_main.cc
void FlutterMain::Init(JNIEnv* env,
                       jclass clazz,
                       jobject context,
                       jobjectArray jargs,
                       jstring bundlePath,
                       jstring appStoragePath,
                       jstring engineCachesPath) {
  std::vector<std::string> args;
  args.push_back("flutter");
  for(auto& arg : fml::jni::StringArrayToVector(env, jargs)) { args.push_back(std::move(arg)); } auto command_line = fml::CommandLineFromIterators(args.begin(), args.end()); auto settings = SettingsFromCommandLine(command_line); settings.assets_path = fml::jni::JavaStringToString(env, bundlePath); . g_flutter_main.reset(new FlutterMain(std::move(settings))); }Copy the code

The c++ layer saves the parameters passed in to the Settings object, and then creates a FlutterMain object from the Settings object and stores it in the global static variable g_flutter_main for subsequent flutter engine initialization.

ViewFactory FlutterActivity (createFlutterView, createFlutterView, createFlutterView, createFlutterView) At this point, the code block in if will be executed. Similarly, the FlutterNativeView object will be created through the viewFactory. When we look at the source code, null will also be returned

  • /flutter/shell/platform/android/io/flutter/app/FlutterActivity.java
@Override
public FlutterView createFlutterView(Context context) {
    return null;
}

@Override
public FlutterNativeView createFlutterNativeView() {
    return null;
}
Copy the code

Next, FlutterView objects are created using FlutterView’s parameterized constructor, which is implemented as follows

  • /flutter/shell/platform/android/io/flutter/view/FlutterView.java
public FlutterView(Context context, AttributeSet attrs, FlutterNativeView nativeView) {
    super(context, attrs);

    Activity activity = (Activity) getContext();
    if (nativeView == null) {
        mNativeView = new FlutterNativeView(activity.getApplicationContext());
    } else{ mNativeView = nativeView; }... mNativeView.attachViewAndActivity(this, activity); mSurfaceCallback = new SurfaceHolder.Callback() { @Override public void surfaceCreated(SurfaceHolder holder) { assertAttached(); mNativeView.getFlutterJNI().onSurfaceCreated(holder.getSurface()); }... }; getHolder().addCallback(mSurfaceCallback); . // Configure the platform plugins and flutter channels. mFlutterLocalizationChannel = new MethodChannel(this,"flutter/localization", JSONMethodCodec.INSTANCE); . }Copy the code

Here, the parameter nativeView has been concluded to be NULL, so the mNativeView object should be created through the constructor of FlutterNativeView. Then the mNativeView calls the attachViewAndActivity method to connect the FlutterView to the current Activity.

Then create the mSurfaceCallback object of the current FlutterView (which inherits from the SurfaceView) and add it to the current SurfaceHolder to listen for Surface changes (such as Surface creation, alteration, and destruction). These changes execute the corresponding callback methods and then pass data to the Flutter Engine layer via FlutterJNI’s related methods.

This method also creates the necessary platform plug-ins and platform channels for various data transfers between flutter and native.

Let’s look at the constructor implementation of FlutterNativeView

  • /flutter/shell/platform/android/io/flutter/view/FlutterNativeView.java
public FlutterNativeView(Context context) {
    this(context, false);
}

public FlutterNativeView(Context context, boolean isBackgroundView) {
    mContext = context;
    mPluginRegistry = new FlutterPluginRegistry(this, context);
    mFlutterJNI = new FlutterJNI();
    mFlutterJNI.setRenderSurface(new RenderSurfaceImpl());
    mFlutterJNI.setPlatformMessageHandler(new PlatformMessageHandlerImpl());
    mFlutterJNI.addEngineLifecycleListener(new EngineLifecycleListenerImpl());
    attach(this, isBackgroundView);
    assertAttached();
    mMessageHandlers = new HashMap<>();
}
Copy the code

Method initializes the mFlutterJNI object. This object is used to pass information to the c++ layer through the JNI methods so that it can tell the flutter engine to perform corresponding actions according to different instructions. This includes creating and starting the Flutter Engine, notifying the current FlutterView of the Surface lifecycle, passing Platform data to the DART layer, and passing back the result data returned by dart layer calling platform layer methods.

One of the key method calls in this constructor is the attach method, which is implemented as follows

  • /flutter/shell/platform/android/io/flutter/view/FlutterNativeView.java
private void attach(FlutterNativeView view, boolean isBackgroundView) {
    mFlutterJNI.attachToNative(isBackgroundView);
}
Copy the code

The connection between Java layer and c++ layer is realized through mFlutterJNI’s attachToNative method as follows

  • /flutter/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java
@UiThread
public void attachToNative(boolean isBackgroundView) {
	ensureNotAttachedToNative();
	nativePlatformViewId = nativeAttach(this, isBackgroundView);
}

private native long nativeAttach(FlutterJNI flutterJNI, boolean isBackgroundView);
Copy the code

In the attachToNative method, the current flutterJNI object is passed to the c++ layer by calling JNI method nativeAttach (some subsequent methods of dart layer calling Java layer are realized by calling corresponding methods of flutterJNI object. NativePlatformViewId = nativePlatformViewId = nativePlatformViewId A series of c++ layer methods are then called with this value to perform operations) and saved for later use.

UML class diagrams for the key Java classes at this stage

Next, take a look at the implementation of the nativeAttach method in c++, which is very important. This is where a series of Flutter engine initializations are made.

  • /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

We find that the method creates STD ::unique_ptr (which holds and manages the AndroidShellHolder object via Pointers) from the previously initialized Settings stored in g_Flutter_main and the passed Java object flutterJNI. 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.

Let’s look at what happens when we create an object using the implementation of the AndroidShellHolder constructor

  • /flutter/shell/platform/android/android_shell_holder.cc
AndroidShellHolder::AndroidShellHolder( blink::Settings settings, fml::jni::JavaObjectWeakGlobalRef java_object, bool is_background_view) : settings_(std::move(settings)), java_object_(java_object) { static size_t shell_count = 1; auto thread_label = std::to_string(shell_count++); .if (is_background_view) {
    thread_host_ = {thread_label, ThreadHost::Type::UI};
  } else{ thread_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) { ...return platform_view_android;
      };
  Shell::CreateCallback<Rasterizer> on_create_rasterizer = [](Shell& shell) {
    return std::make_unique<Rasterizer>(shell.GetTaskRunners());
  };

  // 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 ); shell_ = Shell::Create(task_runners, // task runners settings_, // settings on_create_platform_view, // platform view create callback on_create_rasterizer // rasterizer create callback ); platform_view_ = weak_platform_view; FML_DCHECK(platform_view_); . }}Copy the code

The is_background_view parameter is false. In the first half of the code, we see that three threads will be created and stored in thread_host_, namely gpu_thread, UI_thread, and io_thread. The main UI thread of the platform layer exists as platform_thread. Each of the four threads holds a TaskRunner object corresponding to gpu_runner, UI_Runner, IO_Runner, and platform_runner. These Taskrunners are then used to place some actions into the corresponding threads to be executed. The main responsibilities of each thread in the Flutter Engine are outlined below.

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

We then Create task_runners based on the four threads corresponding to the TaskRunner and shell_ using the Shell::Create() method

  • /flutter/shell/common/shell.cc
std::unique_ptr<Shell> Shell::Create(
    blink::TaskRunners task_runners,
    blink::Settings settings,
    Shell::CreateCallback<PlatformView> on_create_platform_view,
    Shell::CreateCallback<Rasterizer> on_create_rasterizer) {
  PerformInitializationTasks(settings);

  auto vm = blink::DartVM::ForProcess(settings);
  FML_CHECK(vm) << "Must be able to initialize the VM.";
  return Shell::Create(std::move(task_runners),             //
                       std::move(settings),                 //
                       vm->GetIsolateSnapshot(),            //
                       blink::DartSnapshot::Empty(),        //
                       std::move(on_create_platform_view),  //
                       std::move(on_create_rasterizer)      //
  );
}
Copy the code

Initialization tasks (including initialization of trace events bound to SKIA, initialization of the SKIA engine, initialization of internationalization components, etc.) are performed, followed by creation of DartVM objects from Settings values to initialize the Dart VIRTUAL machine, and finally creation of STD :: Unique_ptr and return. Let’s take a look at the creation of DartVM

  • /flutter/runtime/dart_vm.cc
fml::RefPtr<DartVM> DartVM::ForProcess(Settings settings) {
  return ForProcess(settings, nullptr, nullptr, nullptr);
}

static std::once_flag gVMInitialization;
static std::mutex gVMMutex;
static fml::RefPtr<DartVM> gVM;

fml::RefPtr<DartVM> DartVM::ForProcess(
    Settings settings,
    fml::RefPtr<DartSnapshot> vm_snapshot,
    fml::RefPtr<DartSnapshot> isolate_snapshot,
    fml::RefPtr<DartSnapshot> shared_snapshot) {
  std::lock_guard<std::mutex> lock(gVMMutex);
  std::call_once(gVMInitialization, [settings,          //
                                     vm_snapshot,       //
                                     isolate_snapshot,  //
                                     shared_snapshot    //
  ]() mutable {
    if (!vm_snapshot) {
      vm_snapshot = DartSnapshot::VMSnapshotFromSettings(settings);
    }
    ...
    if (!isolate_snapshot) {
      isolate_snapshot = DartSnapshot::IsolateSnapshotFromSettings(settings);
    }
    ...
    gVM = fml::MakeRefCounted<DartVM>(settings,                     //
                                      std::move(vm_snapshot),       //
                                      std::move(isolate_snapshot),  //
                                      std::move(shared_snapshot)    //
    );
  });
  return gVM;
}
Copy the code

This code block means that the code block that creates the DartVM object is executed only once, even if it is called from multiple threads, ensuring that DartVM is initialized only once

  • /flutter/runtime/dart_vm.cc
DartVM::DartVM(const Settings& settings,
               fml::RefPtr<DartSnapshot> vm_snapshot,
               fml::RefPtr<DartSnapshot> isolate_snapshot,
               fml::RefPtr<DartSnapshot> shared_snapshot)
    : settings_(settings),
      vm_snapshot_(std::move(vm_snapshot)),
      isolate_snapshot_(std::move(isolate_snapshot)),
      shared_snapshot_(std::move(shared_snapshot)),
      weak_factory_(this) {
  ...

  {
    TRACE_EVENT0("flutter"."dart::bin::BootstrapDartIo"); dart::bin::BootstrapDartIo(); . }... DartUI::InitForGlobal(); Dart_SetFileModifiedCallback(&DartFileModifiedCallback); { TRACE_EVENT0("flutter"."Dart_Initialize"); Dart_InitializeParams params = {}; . params.create = reinterpret_cast<decltype(params.create)>( DartIsolate::DartIsolateCreateCallback); . char* init_error = Dart_Initialize(&params); . }... }Copy the code

As a first step, by performing a dart: : bin: : BootstrapDartIo start () method to guide “dart: IO” event handler, the specific method call as follows

  • /third_party/dart/runtime/bin/dart_io_api_impl.cc
void BootstrapDartIo() {
  // Bootstrap 'dart:io' event handler.
  TimerUtils::InitOnce();
  EventHandler::Start();
}
Copy the code

The second step is to register dart’s various native methods by executing the DartUI::InitForGlobal() method, which is similar to Java’s JNI method registration. Dart is mainly used to call c++ methods in the dart layer. In the previous article, dart called platform methods using the local method invocation of Window

  • /flutter/lib/ui/dart_ui.cc
void DartUI::InitForGlobal() {
  if(! g_natives) { g_natives = new tonic::DartLibraryNatives(); Canvas::RegisterNatives(g_natives); . FrameInfo::RegisterNatives(g_natives); . Window::RegisterNatives(g_natives); // Secondary isolatesdonot provide UI-related APIs. g_natives_secondary = new tonic::DartLibraryNatives(); DartRuntimeHooks::RegisterNatives(g_natives_secondary); IsolateNameServerNatives::RegisterNatives(g_natives_secondary); }}Copy the code

Just look at the local method registrations associated with Windows

  • /flutter/lib/ui/window/window.cc
void Window::RegisterNatives(tonic::DartLibraryNatives* natives) {
  natives->Register({
      {"Window_defaultRouteName", DefaultRouteName, 1, true},
      {"Window_scheduleFrame", ScheduleFrame, 1, true},
      {"Window_sendPlatformMessage", _SendPlatformMessage, 4, true},
      {"Window_respondToPlatformMessage", _RespondToPlatformMessage, 3, true},
      {"Window_render", Render, 2, true},
      {"Window_updateSemantics", UpdateSemantics, 2, true},
      {"Window_setIsolateDebugName", SetIsolateDebugName, 2, true}}); }Copy the code

Dart_Initialize(& Params) initializes the Dart runtime environment by executing Dart_Initialize(& Params).

  • /third_party/dart/runtime/vm/dart_api_impl.cc
DART_EXPORT char* Dart_Initialize(Dart_InitializeParams* params) {
  ...
  return Dart::Init(params->vm_snapshot_data, params->vm_snapshot_instructions,
                    params->create, params->shutdown, params->cleanup,
                    params->thread_exit, params->file_open, params->file_read,
                    params->file_write, params->file_close,
                    params->entropy_source, params->get_service_assets,
                    params->start_kernel_isolate);
}
Copy the code

The actual initialization is done by the Dart::Init() method

  • /third_party/dart/runtime/vm/dart.cc
char* Dart::Init(const uint8_t* vm_isolate_snapshot, const uint8_t* instructions_snapshot, Dart_IsolateCreateCallback create, Dart_IsolateShutdownCallback shutdown, Dart_IsolateCleanupCallback cleanup, Dart_ThreadExitCallback thread_exit, Dart_FileOpenCallback file_open, Dart_FileReadCallback file_read, Dart_FileWriteCallback file_write, Dart_FileCloseCallback file_close, Dart_EntropySource entropy_source, Dart_GetVMServiceAssetsArchive get_service_assets, bool start_kernel_isolate) { ... FrameLayout::Init(); . OS::Init(); . OSThread::Init(); . Isolate::InitVM(); . Api::Init(); .#if defined(USING_SIMULATOR)
  Simulator::Init();
#endif. thread_pool_ = new ThreadPool(); {... vm_isolate_ = Isolate::InitIsolate("vm-isolate", api_flags, is_vm_isolate); . Object::Init(vm_isolate_); . } Api::InitHandles(); Thread::ExitIsolate(); // Unregister the VM isolate from this thread. ...#ifndef DART_PRECOMPILED_RUNTIME
  if (start_kernel_isolate) {
    KernelIsolate::Run();
  }
#endif // DART_PRECOMPILED_RUNTIME

  return NULL;
}
Copy the code

At this point, the DartVM initialization is complete, and finally the DartVM object is returned to the Shell, which creates the Shell object as follows

  • /flutter/shell/common/shell.cc
std::unique_ptr<Shell> Shell::Create(
    blink::TaskRunners task_runners,
    blink::Settings settings,
    fml::RefPtr<blink::DartSnapshot> isolate_snapshot,
    fml::RefPtr<blink::DartSnapshot> shared_snapshot,
    Shell::CreateCallback<PlatformView> on_create_platform_view,
    Shell::CreateCallback<Rasterizer> on_create_rasterizer) {
  ...
  fml::AutoResetWaitableEvent latch;
  std::unique_ptr<Shell> shell;
  fml::TaskRunner::RunNowOrPostTask(
      task_runners.GetPlatformTaskRunner(),
      [&latch,                                          //
       &shell,                                          //
       task_runners = std::move(task_runners),          //
       settings,                                        //
       isolate_snapshot = std::move(isolate_snapshot),  //
       shared_snapshot = std::move(shared_snapshot),    //
       on_create_platform_view,                         //
       on_create_rasterizer                             //
  ]() {
        shell = CreateShellOnPlatformThread(std::move(task_runners),      //
                                            settings,                     //
                                            std::move(isolate_snapshot),  //
                                            std::move(shared_snapshot),   //
                                            on_create_platform_view,      //
                                            on_create_rasterizer          //
        );
        latch.Signal();
      });
  latch.Wait();
  return shell;
}
Copy the code

Will eventually through the Shell CreateShellOnPlatformThread method on the Platform of Thread created in the Shell object, because this method in the code is more, we block is analyzed, first look at the Shell object creation

  • /flutter/shell/common/shell.cc
auto shell = std::unique_ptr<Shell>(new Shell(task_runners, settings));
Copy the code

The shell object is created through the constructor, followed by the execution of four key code blocks

  • /flutter/shell/common/shell.cc
// Create the platform view on the platform thread (this thread).
auto platform_view = on_create_platform_view(*shell.get());
if(! platform_view || ! platform_view->GetWeakPtr()) {return nullptr;
}
Copy the code
  • /flutter/shell/platform/android/android_shell_holder.cc
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;
    if (is_background_view) {
      ...

    } else {
      platform_view_android = std::make_unique<PlatformViewAndroid>(
          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();
    return platform_view_android;
  };
Copy the code

Key code block 1: Create a PlatformViewAndroid object from the on_create_platform_view function passed in to the platform Thread and assign it to platform_view. The above function is declared in the constructor of AndroidShellHolder.

  • /flutter/shell/common/shell.cc
// Create the IO manager on the IO thread. 
fml::AutoResetWaitableEvent io_latch;
std::unique_ptr<IOManager> io_manager;
auto io_task_runner = shell->GetTaskRunners().GetIOTaskRunner();
fml::TaskRunner::RunNowOrPostTask(
  io_task_runner,
  [&io_latch,       //
   &io_manager,     //
   &platform_view,  //
   io_task_runner   //
]() {
    io_manager = std::make_unique<IOManager>(
        platform_view->CreateResourceContext(), io_task_runner);
    io_latch.Signal();
  });
io_latch.Wait();
Copy the code

Create IOManager objects in IO thread and delegate them to IO_manager.

  • /flutter/shell/common/shell.cc
// Create the rasterizer on the GPU thread.
fml::AutoResetWaitableEvent gpu_latch;
std::unique_ptr<Rasterizer> rasterizer;
fml::WeakPtr<blink::SnapshotDelegate> snapshot_delegate;
fml::TaskRunner::RunNowOrPostTask(
  task_runners.GetGPUTaskRunner(), [&gpu_latch,            //
                                    &rasterizer,           //
                                    on_create_rasterizer,  //
                                    shell = shell.get(),   //
                                    &snapshot_delegate     //
]() {
    if (auto new_rasterizer = on_create_rasterizer(*shell)) {
      rasterizer = std::move(new_rasterizer);
      snapshot_delegate = rasterizer->GetSnapshotDelegate();
    }
    gpu_latch.Signal();
  });

gpu_latch.Wait();
Copy the code
  • /flutter/shell/platform/android/android_shell_holder.cc
Shell::CreateCallback<Rasterizer> on_create_rasterizer = [](Shell& shell) {
return std::make_unique<Rasterizer>(shell.GetTaskRunners());
};
Copy the code

Create a Rasterizer object based on the on_create_rasterizer function passed in to the GPU thread and let the Rasterizer manage it. This function is also declared in the constructor of AndroidShellHolder.

  • /flutter/shell/common/shell.cc
// Create the engine on the UI thread.
fml::AutoResetWaitableEvent ui_latch;
std::unique_ptr<Engine> engine;
fml::TaskRunner::RunNowOrPostTask(
  shell->GetTaskRunners().GetUITaskRunner(),
  fml::MakeCopyable([&ui_latch,                                         //
                     &engine,                                           //
                     shell = shell.get(),                               //
                     isolate_snapshot = std::move(isolate_snapshot),    //
                     shared_snapshot = std::move(shared_snapshot),      //
                     vsync_waiter = std::move(vsync_waiter),            //
                     snapshot_delegate = std::move(snapshot_delegate),  //
                     io_manager = io_manager->GetWeakPtr()              //
]() mutable {
    const auto& task_runners = shell->GetTaskRunners();

    // The animator is owned by the UI thread but it gets its vsync pulses
    // from the platform.
    auto animator = std::make_unique<Animator>(*shell, task_runners,
                                               std::move(vsync_waiter));

    engine = std::make_unique<Engine>(*shell,                        //
                                      shell->GetDartVM(),            //
                                      std::move(isolate_snapshot),   //
                                      std::move(shared_snapshot),    //
                                      task_runners,                  //
                                      shell->GetSettings(),          //
                                      std::move(animator),           //
                                      std::move(snapshot_delegate),  //
                                      std::move(io_manager)          //
    );
    ui_latch.Signal();
  }));

ui_latch.Wait();
Copy the code

Key code block 4: Create Engine objects in UI Thread and delegate them to Engine.

Finally, unique_PTR platform_view, IO_manager, Rasterizer and Engine are saved to shell objects for shell object management through shell Setup method call

  • /flutter/shell/common/shell.cc
if(! shell->Setup(std::move(platform_view), // std::move(engine), // std::move(rasterizer), // std::move(io_manager)) // ) {return nullptr;
}
Copy the code

The Shell object is created by Shell::Create() and returned to AndroidShellHolder. At this point, the Embedder layer connects to the Engine layer through the Shell object, and everything else can be done through the Shell object. The pointer to the Created AndroidShellHolder object is then returned to the Java layer and the Java layer can use the pointer to get the Embedder layer AndroidShellHolder object through the JNI method call. It then sends a series of instructions to the Engine layer through Shell objects.

UML class diagrams for the key c++ classes at this stage

Dart layer code execution in FLUTTER

Now that the process has created the runtime environment for the DART layer code execution, it’s time to load the DART layer related code execution so that we can see the Dart-written widgets displayed on the MainActivity interface.

Let’s go back to the onCreate() method of FlutterActivityDelegate analyzed above. When FlutterView and FlutterNativeView are created successfully, Through the activity. The setContentView (flutterView); FlutterView is used as the content view of the activity, and the UI of the flutter layer is rendered to The FlutterView, so the current MainActivity displays our FLUTTER UI.

Of course, at this point in the code, the DART layer code is not running, so the screen is still blank. Let’s look at the last part of the onCreate() code block, find the appBundle and start executing it through the runBundle method, which looks like this

  • /flutter/shell/platform/android/io/flutter/app/FlutterActivityDelegate.java
private void runBundle(String appBundlePath) {
    if(! flutterView.getFlutterNativeView().isApplicationRunning()) { FlutterRunArguments args = new FlutterRunArguments(); ArrayList<String> bundlePaths = new ArrayList<>(); ResourceUpdater resourceUpdater = FlutterMain.getResourceUpdater();if(resourceUpdater ! = null) { File patchFile = resourceUpdater.getInstalledPatch(); JSONObject manifest = resourceUpdater.readManifest(patchFile);if (resourceUpdater.validateManifest(manifest)) {
                bundlePaths.add(patchFile.getPath());
            }
        }
        bundlePaths.add(appBundlePath);
        args.bundlePaths = bundlePaths.toArray(new String[0]);
        args.entrypoint = "main"; flutterView.runFromBundle(args); }}Copy the code

The first time the flutter page is started isApplicationRunning() is false. After executing the if statement, the code block is checked to see if there are any updated flutter related resources. Should not work), there is no update of bundle pack, the set up corresponding operation parameters, and then use the flutterView. RunFromBundle begin to execute () method.

  • /flutter/shell/platform/android/io/flutter/view/FlutterView.java
public void runFromBundle(FlutterRunArguments args) {
	assertAttached();
	preRun();
	mNativeView.runFromBundle(args);
	postRun();
}
Copy the code

Call the runFromBundle method of the FlutterNativeView

  • /flutter/shell/platform/android/io/flutter/view/FlutterNativeView.java
public void runFromBundle(FlutterRunArguments args) {
    boolean hasBundlePaths = args.bundlePaths != null && args.bundlePaths.length != 0;
    ...
    if (hasBundlePaths) {
        runFromBundleInternal(args.bundlePaths, args.entrypoint, args.libraryPath);
    } else{... } } private void runFromBundleInternal(String[] bundlePaths, String entrypoint, String libraryPath) { ... mFlutterJNI.runBundleAndSnapshotFromLibrary( bundlePaths, entrypoint, libraryPath, mContext.getResources().getAssets() ); applicationIsRunning =true;
}
Copy the code

Finally, JNI method execution is called through FlutterJNI’s methods

  • /flutter/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java
@UiThread
public void runBundleAndSnapshotFromLibrary(
  @NonNull String[] prioritizedBundlePaths,
  @Nullable String entrypointFunctionName,
  @Nullable String pathToEntrypointFunction,
  @NonNull AssetManager assetManager
) {
	ensureAttachedToNative();
	nativeRunBundleAndSnapshotFromLibrary(
	    nativePlatformViewId,
	    prioritizedBundlePaths,
	    entrypointFunctionName,
	    pathToEntrypointFunction,
	    assetManager
	);
}

private native void nativeRunBundleAndSnapshotFromLibrary(
  long nativePlatformViewId,
  @NonNull String[] prioritizedBundlePaths,
  @Nullable String entrypointFunctionName,
  @Nullable String pathToEntrypointFunction,
  @NonNull AssetManager manager
);
Copy the code

So here we see that we’re calling the JNI method and passing in the nativePlatformViewId parameter, which is the pointer to the AndroidShellHolder object that we mentioned earlier, There is only one value in the array like “/data/data/ package name /flutter/flutter_assets/”. EntrypointFunctionName is “main”, PathToEntrypointFunction is null. Let’s take a look at the implementation of JNI’s corresponding c++ methods

  • /flutter/shell/platform/android/platform_view_android_jni.cc
static void RunBundleAndSnapshotFromLibrary(JNIEnv* env,
                                            jobject jcaller,
                                            jlong shell_holder,
                                            jobjectArray jbundlepaths,
                                            jstring jEntrypoint,
                                            jstring jLibraryUrl,
                                            jobject jAssetManager) {
  auto asset_manager = std::make_shared<blink::AssetManager>();
  for (const auto& bundlepath :
       fml::jni::StringArrayToVector(env, jbundlepaths)) {
    ...
    const auto file_ext_index = bundlepath.rfind(".");
    if (bundlepath.substr(file_ext_index) == ".zip") {... }else {
      asset_manager->PushBack(
          std::make_unique<blink::DirectoryAssetBundle>(fml::OpenDirectory(
              bundlepath.c_str(), false, fml::FilePermission::kRead))); . } } auto isolate_configuration = CreateIsolateConfiguration(*asset_manager); . RunConfiguration config(std::move(isolate_configuration), std::move(asset_manager)); { auto entrypoint = fml::jni::JavaStringToString(env, jEntrypoint); auto libraryUrl = fml::jni::JavaStringToString(env, jLibraryUrl);if ((entrypoint.size() > 0) && (libraryUrl.size() > 0)) {
      ...
    } else if (entrypoint.size() > 0) {
      config.SetEntrypoint(std::move(entrypoint));
    }
  }

  ANDROID_SHELL_HOLDER->Launch(std::move(config));
}
Copy the code

This code first sets the information in the loop jBundlePaths to create the DirectoryAssetBundle object based on the BundlePath to be managed by the Asset_manager, then creates the run configuration object Config, Android_shell_launch (STD ::move(config)); Start based on the run configuration information. Note that ANDROID_SHELL_HOLDER is a macro implemented as

  • /flutter/shell/platform/android/platform_view_android_jni.cc
#define ANDROID_SHELL_HOLDER \
  (reinterpret_cast<shell::AndroidShellHolder*>(shell_holder))
Copy the code

That is, the value of the pointer to AndroidShellHolder held by the Java layer is forcibly converted to the pointer to the Object of AndroidShellHolder. At this time, the object pointer can call its method to perform the required operation. Let’s look at the implementation of the Launch method

  • /flutter/shell/platform/android/android_shell_holder.cc
void AndroidShellHolder::Launch(RunConfiguration config) {
  ...
  shell_->GetTaskRunners().GetUITaskRunner()->PostTask(
      fml::MakeCopyable([engine = shell_->GetEngine(),  //
                         config = std::move(config)     //
  ]() mutable {
        ...
        if(! engine || engine->Run(std::move(config)) == shell::Engine::RunStatus::Failure) { ... }else{... }})); }Copy the code

We can see that Engine runs the DART layer code through UITaskRunner in the UI Thread. This is the main purpose of creating a UI Thread. Let’s look at engine’s Run method

  • /flutter/shell/common/engine.cc
Engine::RunStatus Engine::Run(RunConfiguration configuration) { ... auto isolate_launch_status = PrepareAndLaunchIsolate(std::move(configuration)); .returnisolate_running ? Engine::RunStatus::Success : Engine::RunStatus::Failure; } shell::Engine::RunStatus Engine::PrepareAndLaunchIsolate( RunConfiguration configuration) { ... auto isolate_configuration = configuration.TakeIsolateConfiguration(); std::shared_ptr<blink::DartIsolate> isolate = runtime_controller_->GetRootIsolate().lock(); .if (configuration.GetEntrypointLibrary().empty()) {
    if (!isolate->Run(configuration.GetEntrypoint())) {
      ...
    }
  } else{... }return RunStatus::Success;
}
Copy the code

It is eventually executed through DartIsolate’s Run method

  • /flutter/runtime/dart_isolate.cc
bool DartIsolate::Run(const std::string& entrypoint_name) { ... Dart_Handle entrypoint = Dart_GetField(Dart_RootLibrary(), tonic::ToDart(entrypoint_name.c_str())); . Dart_Handle isolate_lib = Dart_LookupLibrary(tonic::ToDart("dart:isolate"));
  if (tonic::LogIfError(isolate_lib)) {
    return false;
  }

  Dart_Handle isolate_args[] = {
      entrypoint,
      Dart_Null(),
  };

  if (tonic::LogIfError(Dart_Invoke(
          isolate_lib, tonic::ToDart("_startMainIsolate"),
          sizeof(isolate_args) / sizeof(isolate_args[0]), isolate_args))) {
    return false; }...return true;
}
Copy the code

Dart handle entryPoint is created by entrypoint_name. Dart_LookupLibrary is used to find the isolate_lib handle of “Dart: ISOLATE”. Note the isolate_args[] handle array. The first value is entryPoint and the second value is Dart_Null(), which is then invoked via the Dart_Invoke method

  • /third_party/dart/runtime/vm/dart_api_impl.cc
DART_EXPORT Dart_Handle Dart_Invoke(Dart_Handle target, Dart_Handle name, int number_of_arguments, Dart_Handle* arguments) { ... String& function_name = String::Handle(Z, Api::UnwrapStringHandle(Z, name).raw()); . const Object& obj = Object::Handle(Z, Api::UnwrapHandle(target)); .if (obj.IsType()) {
    ...
  } else if (obj.IsNull() || obj.IsInstance()) {
    ...
  } else if(obj.IsLibrary()) { // Check whether class finalization is needed. const Library& lib = Library::Cast(obj); .if(Library::IsPrivate(function_name)) { function_name = lib.PrivateName(function_name); }...return Api::NewHandle(
        T, lib.Invoke(function_name, args, arg_names, respect_reflectable));
  } else{... }}Copy the code

Dart_LookupLibrary, the creation method of isolate_lib, is a Library object. Finally, lib.invoke () is used to execute the DART method

  • /third_party/dart/runtime/lib/isolate_patch.dart
@pragma("vm:entry-point")
void _startMainIsolate(Function entryPoint, List<String> args) {
  _startIsolate(
      null, // no parent port
      entryPoint,
      args,
      null, // no message
      true, // isSpawnUri
      null, // no control port
      null); // no capabilities
}
Copy the code

The first parameter entryPoint is the main() entry function found in” main”, which is the main() function in the main. Dart file we wrote, and args is null, which is finally run by calling the following _startIsolate method.

  • /third_party/dart/runtime/lib/isolate_patch.dart
@pragma("vm:entry-point")
void _startIsolate(
    SendPort parentPort,
    Function entryPoint,
    List<String> args,
    var message,
    bool isSpawnUri,
    RawReceivePort controlPort,
    List capabilities) {
  if(controlPort ! = null) { controlPort.handler = (_) {}; // Nobody home on the control port. } ... RawReceivePort port = new RawReceivePort(); port.handler = (_) { port.close();if (isSpawnUri) {
      if (entryPoint is _BinaryFunction) {
        (entryPoint as dynamic)(args, message);
      } else if (entryPoint is _UnaryFunction) {
        (entryPoint as dynamic)(args);
      } else{ entryPoint(); }}else{ entryPoint(message); }}; // Make sure the message handler is triggered. port.sendPort.send(null); }Copy the code

Dart defines the main() function without any parameters, and we call the main() function directly via entryPoint(). At this point, our DART layer code is up and running. This is followed by a call to the runApp() method in the main() function to perform the various widget-related bindings, the creation of the Element, the creation of the RenderObject, and the synthesis of the frame data for rendering to the SurfaceView when the next gsync signal is received.

IOS platform code analysis

IOS corresponding Embedder layer code in engine source code/flutter/shell/platform/Darwin/directory

We also analyzed the startup process of a flutter on iOS platform according to the Demo of the Flutter project created by the flutter create my_app command.

AppDelegate inherits from FlutterAppDelegate. Let’s take a look at the life cycle execution of FlutterAppDelegate

  • /flutter/shell/platform/darwin/ios/framework/Source/FlutterAppDelegate.mm
- (instancetype)init {
  if (self = [super init]) {
    _lifeCycleDelegate = [[FlutterPluginAppLifeCycleDelegate alloc] init];
  }
  return self;
}

- (BOOL)application:(UIApplication*)application
    willFinishLaunchingWithOptions:(NSDictionary*)launchOptions {
  return [_lifeCycleDelegate application:application willFinishLaunchingWithOptions:launchOptions];
}

- (BOOL)application:(UIApplication*)application
    didFinishLaunchingWithOptions:(NSDictionary*)launchOptions {
  return [_lifeCycleDelegate application:application didFinishLaunchingWithOptions:launchOptions];
}
Copy the code

Similar to Android, the FluttAppDelegate lifecycle method is handled by the _lifeCycleDelegate delegate delegate class. Init is used to initialize this object

  • /flutter/shell/platform/darwin/ios/framework/Source/FlutterPluginAppLifeCycleDelegate.mm
static const char* kCallbackCacheSubDir = "Library/Caches/";

- (instancetype)init {
  if (self = [super init]) {
    std::string cachePath = fml::paths::JoinPaths({getenv("HOME"), kCallbackCacheSubDir});
    [FlutterCallbackCache setCachePath:[NSString stringWithUTF8String:cachePath.c_str()]];
    _pluginDelegates = [[NSPointerArray weakObjectsPointerArray] retain];
  }
  return self;
}

- (BOOL)application:(UIApplication*)application
    didFinishLaunchingWithOptions:(NSDictionary*)launchOptions {
  for (id<FlutterPlugin> plugin in [_pluginDelegates allObjects]) {
    if(! plugin) {continue; }... }return YES;
}

- (BOOL)application:(UIApplication*)application
    willFinishLaunchingWithOptions:(NSDictionary*)launchOptions {
  blink::DartCallbackCache::LoadCacheFromDisk();
  for (id<FlutterPlugin> plugin in [_pluginDelegates allObjects]) {
    if(! plugin) {continue; }... }return YES;
}
Copy the code

A cache directory is retrieved in the initialization method and set to FlutterCallbackCache. The two life cycle delegate methods will traverse the objects in _pluginDelegates, but at this point there is no information in the array. If we look at main.storyboard, we’ll see that the application rootViewController is FlutterViewController, so let’s look at the initialization and lifecycle methods of the FlutterViewController

  • /flutter/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm
- (instancetype)initWithProject:(FlutterDartProject*)projectOrNil
                        nibName:(NSString*)nibNameOrNil
                         bundle:(NSBundle*)nibBundleOrNil {
  self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
  if (self) {
    _viewOpaque = YES;
    _weakFactory = std::make_unique<fml::WeakPtrFactory<FlutterViewController>>(self);
    _engine.reset([[FlutterEngine alloc] initWithName:@"io.flutter"
                                              project:projectOrNil
                               allowHeadlessExecution:NO]);
    _flutterView.reset([[FlutterView alloc] initWithDelegate:_engine opaque:self.isViewOpaque]);
    [_engine.get() createShell:nil libraryURI:nil];
    _engineNeedsLaunch = YES;
    [self loadDefaultSplashScreenView];
    [self performCommonViewControllerInitialization];
  }

  return self;
}
Copy the code

FlutterEngine object and FlutterView object will be created in the initialization method, and Shell object will be created based on FlutterEngine object. Let’s first look at the creation of FlutterEngine object

  • /flutter/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm
- (instancetype)initWithName:(NSString*)labelPrefix
                     project:(FlutterDartProject*)projectOrNil
      allowHeadlessExecution:(BOOL)allowHeadlessExecution {
  ...

  _allowHeadlessExecution = allowHeadlessExecution;
  _labelPrefix = [labelPrefix copy];

  _weakFactory = std::make_unique<fml::WeakPtrFactory<FlutterEngine>>(self);

  if (projectOrNil == nil)
    _dartProject.reset([[FlutterDartProject alloc] init]);
  else
    _dartProject.reset([projectOrNil retain]);

  _pluginPublications = [NSMutableDictionary new];
  _platformViewsController.reset(new shell::FlutterPlatformViewsController());

  [self setupChannels];

  return self;
}
Copy the code

If the projectOrNil is passed in nil, the FlutterDartProject object is created and saved to _dartProject, and the FlutterDartProject is initialized

  • /flutter/shell/platform/darwin/ios/framework/Source/FlutterDartProject.mm
static blink::Settings DefaultSettingsForProcess(NSBundle* bundle = nil) { auto command_line = shell::CommandLineFromNSProcessInfo(); NSBundle* mainBundle = [NSBundle mainBundle]; NSBundle* engineBundle = [NSBundle bundleForClass:[FlutterViewController class]]; . auto settings = shell::SettingsFromCommandLine(command_line); settings.task_observer_add = [](intptr_t key, fml::closure callback) { fml::MessageLoop::GetCurrent().AddTaskObserver(key, std::move(callback)); }; settings.task_observer_remove = [](intptr_t key) { fml::MessageLoop::GetCurrent().RemoveTaskObserver(key); }; .return settings;
}

- (instancetype)init {
  return [self initWithPrecompiledDartBundle:nil];
}

- (instancetype)initWithPrecompiledDartBundle:(NSBundle*)bundle {
  self = [super init];

  if (self) {
    _precompiledDartBundle.reset([bundle retain]);
    _settings = DefaultSettingsForProcess(bundle);
  }

  return self;
}
Copy the code

DefaultSettingsForProcess initialization method will be called in c + + code method to complete _settings object initialization, mainly a variety of flutter resource path set (international library, framework, library, etc.) and some other subsequent needed information configuration.

Then go back to the FlutterEngine initialization method and continue creating various Platform channels.

Let’s look at the creation of the FlutterView object

  • /flutter/shell/platform/darwin/ios/framework/Source/FlutterView.mm
- (instancetype)initWithDelegate:(id<FlutterViewEngineDelegate>)delegate opaque:(BOOL)opaque {
  FML_DCHECK(delegate) << "Delegate must not be nil.";
  self = [super initWithFrame:CGRectNull];

  if (self) {
    _delegate = delegate;
    self.layer.opaque = opaque;
  }

  return self;
}
Copy the code

Do a few things, mainly introduced to hold and implements a FlutterViewEngineDelegate FlutterEngine object of the agreement. Finally, let’s look at Shell creation

  • /flutter/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm
- (BOOL)createShell:(NSString*)entrypoint libraryURI:(NSString*)libraryURI {
  ...

  static size_t shellCount = 1;
  auto settings = [_dartProject.get() settings];

  if (libraryURI) {
    ...
  } else if (entrypoint) {
    ...
  } else {
    settings.advisory_script_entrypoint = std::string("main");
    settings.advisory_script_uri = std::string("main.dart");
  }

  const auto threadLabel = [NSString stringWithFormat:@"%@.%zu", _labelPrefix, shellCount++];
  
  fml::MessageLoop::EnsureInitializedForCurrentThread();

  _threadHost = {
      threadLabel.UTF8String,  // label
      shell::ThreadHost::Type::UI | shell::ThreadHost::Type::GPU | shell::ThreadHost::Type::IO};

  shell::Shell::CreateCallback<shell::PlatformView> on_create_platform_view =
      [](shell::Shell& shell) {
        return std::make_unique<shell::PlatformViewIOS>(shell, shell.GetTaskRunners());
      };

  shell::Shell::CreateCallback<shell::Rasterizer> on_create_rasterizer = [](shell::Shell& shell) {
    return std::make_unique<shell::Rasterizer>(shell.GetTaskRunners());
  };

  if (shell::IsIosEmbeddedViewsPreviewEnabled()) {
    ...
  } else{ blink::TaskRunners task_runners(threadLabel.UTF8String, // label fml::MessageLoop::GetCurrent().GetTaskRunner(), // platform _threadHost.gpu_thread->GetTaskRunner(), // gpu _threadHost.ui_thread->GetTaskRunner(), // ui _threadHost.io_thread->GetTaskRunner() // io ); // Create the shell. This is a blocking operation. _shell = shell::Shell::Create(std::move(task_runners), // task runners std::move(settings), // settings on_create_platform_view, // platform view creation on_create_rasterizer // rasterzier creation ); }...return_shell ! = nullptr; }Copy the code

The implementation of this method is similar to the implementation of the AndroidShellHolder constructor in Android, which mainly creates three new threads gpu_thread, UI_thread and io_thread, plus the PLATFORM UI thread as platform_thread, a total of four key threads. You can refer to the instructions in Android for threads. The engine layer Shell object is then created using the Shell::Create() method. The engine initialization, DartVM initialization, and a number of other object creations are all the same as those analyzed above for Android and won’t be covered here.

Looking back at the initialization method in The FlutterViewController, after the above sequence of actions, the necessary notifications are registered in order to respond when received.

The initialization of FlutterViewController is now complete. It can be seen that the whole initialization is the initialization of flutter Engine. Where is the iOS DART layer code executed? Let’s look at the FlutterViewController’s lifecycle method, the viewWillAppear method

  • /flutter/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm
- (void)viewWillAppear:(BOOL)animated {
  TRACE_EVENT0("flutter"."viewWillAppear");

  if (_engineNeedsLaunch) {
    [_engine.get() launchEngine:nil libraryURI:nil];
    _engineNeedsLaunch = NO;
  }
  [_engine.get() setViewController:self];

  if (_viewportMetrics.physical_width)
    [self surfaceUpdated:YES];
  [[_engine.get() lifecycleChannel] sendMessage:@"AppLifecycleState.inactive"];

  [super viewWillAppear:animated];
}
Copy the code

Where _engineNeedsLaunch is set to YES at FlutterViewController initialization, _engine will start the engine

  • /flutter/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm
- (void)launchEngine:(NSString*)entrypoint libraryURI:(NSString*)libraryOrNil {
  // Launch the Dart application with the inferred run configuration.
  self.shell.GetTaskRunners().GetUITaskRunner()->PostTask(fml::MakeCopyable(
      [engine = _shell->GetEngine(),
       config = [_dartProject.get() runConfigurationForEntrypoint:entrypoint
                                                     libraryOrNil:libraryOrNil]  //
  ]() mutable {
        if (engine) {
          auto result = engine->Run(std::move(config));
          if (result == shell::Engine::RunStatus::Failure) {
            FML_LOG(ERROR) << "Could not launch engine with configuration."; }}})); }Copy the code

Here the Engine runs the DART layer code in the UI Thread via UITaskRunner, which functions the same as the Launch method in the Android Embedder layer AndroidShellHolder. But we found that this method has nil for both arguments, and android entrypoint is “main”, so how does iOS end up executing the Dart application that does main(), so let’s look at the declarations of some variables in the RunConfiguration class

  • /flutter/shell/common/run_configuration.cc
class RunConfiguration { public: ... RunConfiguration(RunConfiguration&&); ~RunConfiguration(); . const std::string& GetEntrypoint() const; const std::string& GetEntrypointLibrary() const; . private: ... std::string entrypoint_ ="main";
  std::string entrypoint_library_ = "";

  FML_DISALLOW_COPY_AND_ASSIGN(RunConfiguration);
}
Copy the code

It turns out that the RunConfiguration object is created with entryPoint as “main” by default, so instead of actively setting entryPoint, the Dart code ends up executing the main() method as an entry function. Some of the engine layer’s subsequent operations are the same as android’s and won’t be covered here.

The key UML class diagrams for objective-C and C ++ classes in this phase

conclusion

From the above source code analysis for Android and iOS platforms, we have gained a general understanding of how the Flutter application is started. How the widgets written in Dart are eventually drawn onto the platform View can be found in our article drawing the Flutter View. I’m sure you’ll get the answer you want.

Description:

This article is reprinted from the corresponding “Flutter Programming Guide” wechat official account. For more Flutter related articles, open our wechat and scan our QR code to follow our wechat official account.