Abstract

This article is mainly about the source code of Flutter on iOS, summarizing the general process of Flutter operation.

The key classes involved are as follows:

  • FlutterViewController
  • FlutterView
  • FlutterEngine
  • DartIsolate

FlutterViewController

The native application of Flutter embedding must have a carrier. Starting from this point, the entry point of the API in the Flutter Engine source is the FlutterViewController. The source of its header file is simplified as follows

@interface FlutterViewController : UIViewController <FlutterTextureRegistry.FlutterPluginRegistry>

- (instancetype)initWithEngine:(FlutterEngine*)engine
                       nibName:(nullable NSString*)nibName
                        bundle:(nullable NSBundle*)nibBundle NS_DESIGNATED_INITIALIZER;

- (instancetype)initWithProject:(nullable FlutterDartProject*)project
                        nibName:(nullable NSString*)nibName
                         bundle:(nullable NSBundle*)nibBundle NS_DESIGNATED_INITIALIZER;

- (void)handleStatusBarTouches:(UIEvent*)event;

- (void)setFlutterViewDidRenderCallback:(void(^) (void))callback;

- (NSString*)lookupKeyForAsset:(NSString*)asset;

- (NSString*)lookupKeyForAsset:(NSString*)asset fromPackage:(NSString*)package;

- (void)setInitialRoute:(NSString*)route;

- (void)popRoute;

- (void)pushRoute:(NSString*)route;

- (id<FlutterPluginRegistry>)pluginRegistry;

@property(nonatomic.readonly.getter=isDisplayingFlutterUI) BOOL displayingFlutterUI;

@property(strong.nonatomic) UIView* splashScreenView;

- (BOOL)loadDefaultSplashScreenView;

@property(nonatomic.getter=isViewOpaque) BOOL viewOpaque;

@property(weak.nonatomic.readonly) FlutterEngine* engine;

@property(nonatomic.readonly) NSObject<FlutterBinaryMessenger>* binaryMessenger;

@end
Copy the code

Constructor of FlutterViewController

FlutterViewController has two constructors, essentially the same. The first constructor was opened by Google to enable users to reuse FlutterViewController in situations where there are multiple FlutterViewControllers.

- (instancetype)initWithEngine:(FlutterEngine*)engine
                       nibName:(nullable NSString*)nibName
                        bundle:(nullable NSBundle*)nibBundle {
  NSAssert(engine ! =nil.@"Engine is required");
  self = [super initWithNibName:nibName bundle:nibBundle];
  if (self) {
    _viewOpaque = YES;
    _engine.reset([engine retain]);
    _engineNeedsLaunch = NO;
    _flutterView.reset([[FlutterView alloc] initWithDelegate:_engine opaque:self.isViewOpaque]);
    _weakFactory = std::make_unique<fml::WeakPtrFactory<FlutterViewController>>(self);
    _ongoingTouches = [[NSMutableSet alloc] init];

    [self performCommonViewControllerInitialization];
    [engine setViewController:self];
  }

  return self;
}

- (instancetype)initWithProject:(nullable FlutterDartProject*)project
                        nibName:(nullable NSString*)nibName
                         bundle:(nullable NSBundle*)nibBundle {
  self = [super initWithNibName:nibName bundle:nibBundle];
  if (self) {
    _viewOpaque = YES;
    _weakFactory = std::make_unique<fml::WeakPtrFactory<FlutterViewController>>(self);
    _engine.reset([[FlutterEngine alloc] initWithName:@"io.flutter"
                                              project:project
                               allowHeadlessExecution:NO]);
    _flutterView.reset([[FlutterView alloc] initWithDelegate:_engine opaque:self.isViewOpaque]);
    [_engine.get() createShell:nil libraryURI:nil];
    _engineNeedsLaunch = YES;
    _ongoingTouches = [[NSMutableSet alloc] init];
    [self loadDefaultSplashScreenView];
    [self performCommonViewControllerInitialization];
  }

  return self;
}

Copy the code

There are a few things you do in the constructor:

  • Initializes or replaces the current FlutterEngine

  • Initialize FlutterView

  • Initialize the collection of gestures that are taking place

  • The constructor passed in to FlutterEngine does not have this function. It is considered that multiple FlutterViewControllers cannot load flutterpage frequently

  • Set UIInterfaceOrientationMask and UIStatusBarStyle

  • Add a list of notifications, including Application lifecycle, keyboard events, Accessibility events, etc

  • Set the FlutterViewController to the FlutterEngine

The second constructor has this extra line, and the first constructor just delays the call

    [_engine.get() createShell:nil libraryURI:nil];
Copy the code

The loadView FlutterViewController

In the loadView function, the view of FlutterViewController is set and whether the flash page needs to be loaded is judged. The flash page can be completely not loaded by rewriting the get method of splashScreenView to return nil

- (void)loadView {
  self.view = _flutterView.get();
  self.view.multipleTouchEnabled = YES;
  self.view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;

  [self installSplashScreenViewIfNecessary];
}
Copy the code

FlutterViewController operations on Navigator

The FlutterViewController provides three interfaces that allow us to operate directly on the Dart Navigator from the native end

- (void)setInitialRoute:(NSString*)route {
  [[_engine.get() navigationChannel] invokeMethod:@"setInitialRoute" arguments:route];
}

- (void)popRoute {
  [[_engine.get() navigationChannel] invokeMethod:@"popRoute" arguments:nil];
}

- (void)pushRoute:(NSString*)route {
  [[_engine.get() navigationChannel] invokeMethod:@"pushRoute" arguments:route];
}
Copy the code

setInitialRoute

The setInitialRoute command uses navigationChannel on iOS to tell the Dart the specific initialRoute. This process is a bit special. It does not receive the channel information directly on the DART side. Web_ui is beyond the scope of this article’s parsing; the points related to primitives are washed directly here

The setInitialRoute setup process is as follows:

DispatchPlatformMessage -> HandleNavigationPlatformMessage -> initial_route_

void Engine::DispatchPlatformMessage(fml::RefPtr<PlatformMessage> message) {
  if (message->channel() == kLifecycleChannel) {
    if (HandleLifecyclePlatformMessage(message.get()))
      return;
  } else if (message->channel() == kLocalizationChannel) {
    if (HandleLocalizationPlatformMessage(message.get()))
      return;
  } else if (message->channel() == kSettingsChannel) {
    HandleSettingsPlatformMessage(message.get());
    return;
  }

  if (runtime_controller_->IsRootIsolateRunning() &&
      runtime_controller_->DispatchPlatformMessage(std::move(message))) {
    return;
  }

  // If there's no runtime_, we may still need to set the initial route.
  if (message->channel() == kNavigationChannel) {
    HandleNavigationPlatformMessage(std::move(message));
    return;
  }

  FML_DLOG(WARNING) << "Dropping platform message on channel: "
                    << message->channel();
}
Copy the code
bool Engine::HandleNavigationPlatformMessage(
    fml::RefPtr<PlatformMessage> message) {
  const auto& data = message->data();

  rapidjson::Document document;
  document.Parse(reinterpret_cast<const char*>(data.data()), data.size());
  if(document.HasParseError() || ! document.IsObject())return false;
  auto root = document.GetObject();
  auto method = root.FindMember("method");
  if(method->value ! ="setInitialRoute")
    return false;
  auto route = root.FindMember("args");
  initial_route_ = std::move(route->value.GetString());
  return true;
}
Copy the code

SetInitialRoute directly in HandleNavigationPlatformMessage functions eventually be assigned to initial_route_.

The setInitialRoute reading process is as follows:

Window.defaultRouteName -> DefaultRouteName -> Engine::DefaultRouteName -> initial_route_

As you can see, the keyword native, which dart added to make it easier to bind C/C++ export methods, is Window_defaultRouteName

class Window {
  String get defaultRouteName => _defaultRouteName();
  String _defaultRouteName() native 'Window_defaultRouteName';
}
Copy the code

In the engine layer of the flutter namespace, the following function registers the corresponding export function, in this case DefaultRouteName

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},
      {"Window_reportUnhandledException", ReportUnhandledException, 2.true},
      {"Window_setNeedsReportTimings", SetNeedsReportTimings, 2.true}}); }Copy the code
void DefaultRouteName(Dart_NativeArguments args) {
  std: :string routeName =
      UIDartState::Current()->window()->client()->DefaultRouteName();
  Dart_SetReturnValue(args, tonic::StdStringToDart(routeName));
}
Copy the code

Further down is the function below the engine.cc file that reads the value of initial_route_

std: :string Engine::DefaultRouteName() {
  if(! initial_route_.empty()) {return initial_route_;
  }
  return "/";
}
Copy the code

This completes the process of setting defaultRouteName on the native side and getting the value on the DART side.

pushRoute and popRoute

Dart is notified by the built-in navigationChannel, and the corresponding channel exists in the Dart SystemChannels class

static const MethodChannel navigation = MethodChannel(
      'flutter/navigation',
      JSONMethodCodec(),
  );
Copy the code

The logic that ultimately handles pushRoute and popRoute is in the WidgetsBinding class, mainly the following functions

  Future<dynamic> _handleNavigationInvocation(MethodCall methodCall) {
    switch (methodCall.method) {
      case 'popRoute':
        return handlePopRoute();
      case 'pushRoute':
        return handlePushRoute(methodCall.arguments as String);
    }
    return Future<dynamic>.value();
  }

  Future<void> handlePushRoute(String route) async {
    for (final WidgetsBindingObserver observer in List<WidgetsBindingObserver>.from(_observers)) {
      if (await observer.didPushRoute(route))
        return;
    }
  }

  Future<void> handlePopRoute() async {
    for (final WidgetsBindingObserver observer in List<WidgetsBindingObserver>.from(_observers)) {
      if (await observer.didPopRoute())
        return;
    }
    SystemNavigator.pop();
  }
Copy the code

This code will interrupt only if the called method returns true. The specific handling logic for each handle is implemented by a WidgetsBindingObserver. Follow up to find the code below

class _WidgetsAppState extends State<WidgetsApp> with WidgetsBindingObserver {

  @override
  Future<bool> didPopRoute() async {
    assert(mounted);
    finalNavigatorState navigator = _navigator? .currentState;if (navigator == null)
      return false;
    return await navigator.maybePop();
  }

  @override
  Future<bool> didPushRoute(String route) async {
    assert(mounted);
    finalNavigatorState navigator = _navigator? .currentState;if (navigator == null)
      return false;
    navigator.pushNamed(route);
    return true; }}Copy the code

In the handlePopRoute function, if none of the observers returns true, systemNavigator.pop () is finally called; To exit the application

class SystemNavigator {
  static Future<void> pop({bool animated}) async {
    await SystemChannels.platform.invokeMethod<void> ('SystemNavigator.pop', animated); }}Copy the code

FlutterView

FlutterView does not have many functions. There are two main points:

  • Passed in during initializationFlutterViewEngineDelegate
  • createflutter::IOSSurface
@protocol FlutterViewEngineDelegate <NSObject>

- (flutter::Rasterizer::Screenshot)takeScreenshot:(flutter::Rasterizer::ScreenshotType)type
                                  asBase64Encoded:(BOOL)base64Encode;

- (flutter::FlutterPlatformViewsController*)platformViewsController;

@end

@interface FlutterView : UIView

- (instancetype)initWithDelegate:(id<FlutterViewEngineDelegate>)delegate
                          opaque:(BOOL)opaque NS_DESIGNATED_INITIALIZER;
- (std::unique_ptr<flutter::IOSSurface>)createSurface:
    (std::shared_ptr<flutter::IOSGLContext>)context;

@end
Copy the code

TakeScreenshot: asBase64Encoded: should be FlutterView rendering the data source, specific reference drawLayer: inContext: the source code

@implementation FlutterView
- (void)drawLayer:(CALayer*)layer inContext:(CGContextRef)context {
  if(layer ! =self.layer || context == nullptr) {
    return;
  }

  auto screenshot = [_delegate takeScreenshot:flutter::Rasterizer::ScreenshotType::UncompressedImage
                              asBase64Encoded:NO];

  if(! screenshot.data || screenshot.data->isEmpty() || screenshot.frame_size.isEmpty()) {return;
  }

  NSData* data = [NSData dataWithBytes:const_cast<void*>(screenshot.data->data())
                                length:screenshot.data->size()];

  fml::CFRef<CGDataProviderRef> image_data_provider(
      CGDataProviderCreateWithCFData(reinterpret_cast<CFDataRef>(data)));

  fml::CFRef<CGColorSpaceRef> colorspace(CGColorSpaceCreateDeviceRGB());

  fml::CFRef<CGImageRef> image(CGImageCreate(
      screenshot.frame_size.width(),      // size_t width
      screenshot.frame_size.height(),     // size_t height
      8.// size_t bitsPerComponent
      32.// size_t bitsPerPixel,
      4 * screenshot.frame_size.width(),  // size_t bytesPerRow
      colorspace,                         // CGColorSpaceRef space
      static_cast<CGBitmapInfo>(kCGImageAlphaPremultipliedLast |
                                kCGBitmapByteOrder32Big),  // CGBitmapInfo bitmapInfo
      image_data_provider,                                 // CGDataProviderRef provider
      nullptr,                                             // const CGFloat* decode
      false.// bool shouldInterpolate
      kCGRenderingIntentDefault                            // CGColorRenderingIntent intent
      ));

  const CGRect frame_rect =
      CGRectMake(0.0.0.0, screenshot.frame_size.width(), screenshot.frame_size.height());

  CGContextSaveGState(context);
  CGContextTranslateCTM(context, 0.0.CGBitmapContextGetHeight(context));
  CGContextScaleCTM(context, 1.0.1.0);
  CGContextDrawImage(context, frame_rect, image);
  CGContextRestoreGState(context);
}
@end
Copy the code

We will see FlutterViewEngineDelegate behind is actually FlutterEngine has been realized.

I’m not going to parse the IOSSurface too much, it’s built on top of three layers, and you can choose which rendering method to use at compile time

  • If it is an emulator, use normal CALayer
  • For Metal renderings, use CAMetalLayer
  • Scenarios rendered using OpenGL use CAEAGLLayer
+ (Class)layerClass {
#if TARGET_IPHONE_SIMULATOR
  return [CALayer class];
#else // TARGET_IPHONE_SIMULATOR
#if FLUTTER_SHELL_ENABLE_METAL
  return [CAMetalLayer class];
#else // FLUTTER_SHELL_ENABLE_METAL
  return [CAEAGLLayer class];
#endif // FLUTTER_SHELL_ENABLE_METAL
#endif // TARGET_IPHONE_SIMULATOR
}
Copy the code

The createSurface function creates three different ios surfaces

CALayer -> IOSSurfaceSoftware CAEAGLLayer -> IOSSurfaceGL CAMetalLayer -> IOSSurfaceMetal

Rendering further down is actually left to the FlutterEngine itself.

FlutterEngine

FlutterEngine does not expose many interfaces, but these are the main points

  • Constructor,initWithName:project:allowHeadlessExecution, allowHeadlessExecutionAllows engine initialization without strong dependenciesFlutterViewController`
  • Start the engine,runWithEntrypoint:libraryURI:Custom can be passed inentrypoint
  • Freeing up resources,destroyContext
  • Whether the semantic tree is established,ensureSemanticsEnabledThere is little documentation about semantic trees, probably something you need in disabled mode
  • FlutterViewControllerGet/set
  • Finally, there’s a bunch of built-in channels

We are mostly concerned with engine construction, startup, release, and FlutterViewController. FlutterTextureRegistry and FlutterPluginRegistry are beyond the scope of this article

@interface FlutterEngine : NSObject <FlutterTextureRegistry.FlutterPluginRegistry>

- (instancetype)initWithName:(NSString*)labelPrefix
                     project:(nullable FlutterDartProject*)project
      allowHeadlessExecution:(BOOL)allowHeadlessExecution NS_DESIGNATED_INITIALIZER;

- (BOOL)runWithEntrypoint:(nullable NSString*)entrypoint libraryURI:(nullable NSString*)uri;

- (void)destroyContext;

- (void)ensureSemanticsEnabled;

@property(nonatomic.weak) FlutterViewController* viewController;

@property(nonatomic.readonly.nullable) FlutterMethodChannel* localizationChannel;

@property(nonatomic.readonly) FlutterMethodChannel* navigationChannel;

@property(nonatomic.readonly) FlutterMethodChannel* platformChannel;

@property(nonatomic.readonly) FlutterMethodChannel* textInputChannel;

@property(nonatomic.readonly) FlutterBasicMessageChannel* lifecycleChannel;

@property(nonatomic.readonly) FlutterBasicMessageChannel* systemChannel;

@property(nonatomic.readonly) FlutterBasicMessageChannel* settingsChannel;

@property(nonatomic.readonly) NSObject<FlutterBinaryMessenger>* binaryMessenger;

@property(nonatomic.readonly.copy.nullable) NSString* isolateId;

@end
Copy the code

The structure of the FlutterEngine

When constructing FlutterEngine, pay attention to the following two points:

  • FlutterDartProjectInitialize the
  • FlutterPlatformViewsControllerThe initialization
- (instancetype)initWithName:(NSString*)labelPrefix
                     project:(FlutterDartProject*)project
      allowHeadlessExecution:(BOOL)allowHeadlessExecution {
  self = [super init];
  NSAssert(self.@"Super init cannot be nil");
  NSAssert(labelPrefix, @"labelPrefix is required");

  _allowHeadlessExecution = allowHeadlessExecution;
  _labelPrefix = [labelPrefix copy];

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

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

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

  _binaryMessenger = [[FlutterBinaryMessengerRelay alloc] initWithParent:self];

  NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
  [center addObserver:self
             selector:@selector(onMemoryWarning:)
                 name:UIApplicationDidReceiveMemoryWarningNotification
               object:nil];

  return self;
}
Copy the code

FlutterEngine startup

At the FlutterEngine level, you need to pay attention to the following classes:

  • FlutterDartProject
  • flutter::ThreadHost
  • flutter::Shell
  • FlutterObservatoryPublisher
  • FlutterPlatformViewsController

FlutterEngine starts with two things

  • createShell
  • launchEngine
- (BOOL)runWithEntrypoint:(NSString*)entrypoint libraryURI:(NSString*)libraryURI {
  if ([self createShell:entrypoint libraryURI:libraryURI]) {
    [self launchEngine:entrypoint libraryURI:libraryURI];
  }

  return_shell ! = nullptr; }Copy the code

createShell

CreateShell has a lot of source code and has been streamlined to include the following:

  • Initialize theMessageLoop
  • Initialize theThreadHost
  • Set up theon_create_platform_viewThe callback
  • Set up theon_create_rasterizerThe callback
  • Initialize theflutter::TaskRunners, if turned onembedded_views_previewIs used for the current threadTaskRunnerAs a GPU threadTaskRunner
  • createshellAnd finally start the Isolate
  • createFlutterPlatformViewsController
  • createFlutterObservatoryPublisher
  • Set up thePlatformView channels
- (BOOL)createShell:(NSString*)entrypoint libraryURI:(NSString*)libraryURI {

  / /...

  fml::MessageLoop::EnsureInitializedForCurrentThread();

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

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

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

  if (flutter::IsIosEmbeddedViewsPreviewEnabled()) {
    flutter::TaskRunners task_runners(threadLabel.UTF8String,                          // label
                                      fml::MessageLoop::GetCurrent().GetTaskRunner(),  // platform
                                      fml::MessageLoop::GetCurrent().GetTaskRunner(),  // gpu
                                      _threadHost.ui_thread->GetTaskRunner(),          // ui
                                      _threadHost.io_thread->GetTaskRunner()           // io
    );
    // Create the shell. This is a blocking operation.
    _shell = flutter::Shell::Create(std::move(task_runners),  // task runners
                                    std::move(settings),      // settings
                                    on_create_platform_view,  // platform view creation
                                    on_create_rasterizer      // rasterzier creation
    );
  } else {
    flutter::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 = flutter::Shell::Create(std::move(task_runners),  // task runners
                                    std::move(settings),      // settings
                                    on_create_platform_view,  // platform view creation
                                    on_create_rasterizer      // rasterzier creation
    );
  }

  if(_shell ! = nullptr) { [self setupChannels];
    if(! _platformViewsController) { _platformViewsController.reset(new flutter::FlutterPlatformViewsController()); } _publisher.reset([[FlutterObservatoryPublisher alloc] init]); [self maybeSetupPlatformViewChannels];
  }

  return_shell ! = nullptr; }Copy the code

Here you can see that four Taskrunners are launched, namely Platform, GPU, UI and IO, but they do not necessarily correspond to four threads.

launchEngine

LaunchEngine actually operates on the shell

- (void)launchEngine:(NSString*)entrypoint libraryURI:(NSString*)libraryOrNil {
  // Launch the Dart application with the inferred run configuration.
  self.shell.RunEngine([_dartProject.get() runConfigurationForEntrypoint:entrypoint
                                                            libraryOrNil:libraryOrNil]);
}
Copy the code
void Shell::RunEngine(RunConfiguration run_configuration) {
  RunEngine(std::move(run_configuration), nullptr);
}

void Shell::RunEngine(RunConfiguration run_configuration,
                      std::function<void(Engine::RunStatus)> result_callback) {
  auto result = [platform_runner = task_runners_.GetPlatformTaskRunner(),
                 result_callback](Engine::RunStatus run_result) {
    if(! result_callback) {return;
    }
    platform_runner->PostTask(
        [result_callback, run_result]() { result_callback(run_result); });
  };
  FML_DCHECK(is_setup_);
  FML_DCHECK(task_runners_.GetPlatformTaskRunner()->RunsTasksOnCurrentThread());

  if(! weak_engine_) { result(Engine::RunStatus::Failure); } fml::TaskRunner::RunNowOrPostTask( task_runners_.GetUITaskRunner(), fml::MakeCopyable( [run_configuration =std::move(run_configuration),
           weak_engine = weak_engine_, result]() mutable {
            if(! weak_engine) { FML_LOG(ERROR) <<"Could not launch engine with configuration - no engine.";
              result(Engine::RunStatus::Failure);
              return;
            }
            auto run_result = weak_engine->Run(std::move(run_configuration));
            if (run_result == flutter::Engine::RunStatus::Failure) {
              FML_LOG(ERROR) << "Could not launch engine with configuration.";
            }
            result(run_result);
          }));
}
Copy the code

This leads to the run function in [shell > common > engine.cc], the core of which is PrepareAndLaunchIsolate, and the whole process runs to start the Isolate

Engine::RunStatus Engine::Run(RunConfiguration configuration) { if (! configuration.IsValid()) { FML_LOG(ERROR) << "Engine run configuration was invalid."; return RunStatus::Failure; } auto isolate_launch_status = PrepareAndLaunchIsolate(std::move(configuration)); if (isolate_launch_status == Engine::RunStatus::Failure) { FML_LOG(ERROR) << "Engine not prepare and launch isolate."; return isolate_launch_status; } else if (isolate_launch_status == Engine::RunStatus::FailureAlreadyRunning) { return isolate_launch_status; } std::shared_ptr<DartIsolate> isolate = runtime_controller_->GetRootIsolate().lock(); bool isolate_running = isolate && isolate->GetPhase() == DartIsolate::Phase::Running; if (isolate_running) { tonic::DartState::Scope scope(isolate.get()); if (settings_.root_isolate_create_callback) { settings_.root_isolate_create_callback(); } if (settings_.root_isolate_shutdown_callback) { isolate->AddIsolateShutdownCallback( settings_.root_isolate_shutdown_callback); } std::string service_id = isolate->GetServiceId(); fml::RefPtr<PlatformMessage> service_id_message = fml::MakeRefCounted<flutter::PlatformMessage>( kIsolateChannel, std::vector<uint8_t>(service_id.begin(), service_id.end()), nullptr); HandlePlatformMessage(service_id_message); } return isolate_running ? Engine::RunStatus::Success : Engine::RunStatus::Failure; }Copy the code

DartIsolate

The PrepareAndLaunchIsolate function is simplified, leaving two points

  • PrepareIsolate
  • RunFromLibrary
Engine::RunStatus Engine::PrepareAndLaunchIsolate(RunConfiguration configuration) {
  / /...

  if(! isolate_configuration->PrepareIsolate(*isolate)) {return RunStatus::Failure;
  }

  if(! isolate->RunFromLibrary(configuration.GetEntrypointLibrary(), configuration.GetEntrypoint(), settings_.dart_entrypoint_args)) {return RunStatus::Failure;
  }

  return RunStatus::Success;
}
Copy the code

What does RunFromLibrary do

  • Find the entrypoint
  • Call the entryPoint function,InvokeMainEntrypoint
bool DartIsolate::RunFromLibrary(const std: :string& library_name,
                                 const std: :string& entrypoint_name,
                                 const std: :vector<std: :string>& args,
                                 fml::closure on_run) {
  tonic::DartState::Scope scope(this);

  auto user_entrypoint_function =
      Dart_GetField(Dart_LookupLibrary(tonic::ToDart(library_name.c_str())),
                    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();
  }
  return true;
}
Copy the code

Now, what does InvokeMainEntrypoint do? The source code has been simplified. These are the two main steps that we can find on the Dart side

  • _getStartMainIsolateFunction
  • _runMainZoned
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}))) {
    FML_LOG(ERROR) << "Could not invoke the main entrypoint.";
    return false;
  }

  return true;
}
Copy the code

Further down is the tonic library, which is the library under Fuchsia.

conclusion

Flutter runs on iOS. From a source perspective, Flutter has the following benefits:

  • Reusing three existing Calayers to draw interfaces,drawLayerCalled whentakeScreenshotTo obtain a raster image of the Flutter interface
  • The corresponding semantic tree will not be created in the native end, so additional generation is required
  • Flutter itself has a completely independent thread environment to run in. There are four that we need to focus onTaskRunnerPlatform TaskRunner is not necessarily a separate thread
  • Platform TaskRunner, all native interactions with Flutter are handled by Platform TaskRunner
  • The DART end can passnativeThe keyword calls a C/C++ function that retrieves the return value of the primitive type of data and performs better than channel
  • FlutterViewControllerAll gestures related to interaction are forwarded to FlutterEngine

Flutter operation flow

The whole process of Flutter operation can be summarized as follows, mainly focusing on the engine side. The dart process is not expanded for reference only:

  • Looking for DartLibrary
  • Locate the Entrypoint
  • createFlutterEngine, passed to DartLibrary and Entrypoint
  • createFlutterViewController.FlutterView
  • Set up theFlutterEngineviewController
  • Create a shell and start the Dart VM
  • Load dart Library and run Entrypoint for DART
  • Capture and raster the Dart UI interface and draw a CALayer

Other Articles by author

Flutter engine source code interpretation – memory management

Flutter’s innovative business practices at the B-end of Hello Travel

How to seamlessly introduce Flutter into an existing application?