How to run flutter on IOS

Time:2020-10-29

abstract

This paper mainly focuses on how to run flutter on IOS, and summarizes the general running process.

The key classes involved are as follows:

  • FlutterViewController
  • FlutterView
  • FlutterEngine
  • DartIsolate

FlutterViewController

There must be a carrier for the embedded native application of the fluent. Starting from this point, the entry point of the API in the source code of the fluent engine isFlutterViewControllerThe source code of the 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

Constructor for the flutterviewcontroller

Flutterviewcontroller has two constructors, which are essentially the same. The first one is Google to create multiple constructorsFlutterViewControllerIn order to enable users to reuseFlutterEngineAnd open.

- (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;
}

In the constructor, we mainly do the following things:

  • Initializes or replaces the currentFlutterEngine
  • initializationFlutterView
  • Initializes the collection of gestures that are occurring
  • Load flash page, pass inFlutterEngineThe constructor does not have this item. It should be considered a lotFlutterViewControllerIt’s not good to load the flash screen page frequently
  • set upUIInterfaceOrientationMaskandUIStatusBarStyle
  • Add a series of notifications, includingApplicationLife cycle, keyboard events,AccessibilityAnd so on
  • takeFlutterViewControllerSet toFlutterEngine

This line of code is added to the second constructor, and the first one just delays the call

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

Loadview of flutterviewcontroller

stayloadViewFunction, set theFlutterViewControllerOfviewAnd determine whether the flash screen page needs to be loaded, which can be rewrittensplashScreenViewThe get method ofnilDo not load flash pages at all

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

  [self installSplashScreenViewIfNecessary];
}

Operation of navigator by fluterviewcontroller

FlutterViewControllerThree interfaces are provided to allow us to access DART’sNavigatorDirect operation

- (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];
}

setInitialRoute

setInitialRouteThrough the IOS sidenavigationChannelTo tell dart the specific initialroute, this process is slightly special, and does not directly receive channel information at dart end,
It’s done at the engine level, web_ UI is not in the scope of this article’s analysis, here directly wash the points related to the original

setInitialRouteThe setting 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();
}
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;
}

setInitialRouteIn the endHandleNavigationPlatformMessageFunction is directly assigned to theinitial_route_

setInitialRouteThe reading process is as follows:

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

As you can see, keywordnative, which is a keyword added by dart to facilitate the binding of C / C + + export methods. The corresponding key isWindow_defaultRouteName

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

You can find the following function under the fluent namespace of the engine layer. The corresponding export function is registeredDefaultRouteName

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},
  });
}
void DefaultRouteName(Dart_NativeArguments args) {
  std::string routeName =
      UIDartState::Current()->window()->client()->DefaultRouteName();
  Dart_SetReturnValue(args, tonic::StdStringToDart(routeName));
}

It’s down there engine.cc File under the function, readinitial_route_Value of

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

So far, the process of setting the default routename on the native side and getting the value on the dart side is completed.

pushRoute and popRoute

The implementation method is mainly built-in through the enginenavigationChannelNotify dart side, corresponding to dart sideSystemChannelsClass, there is a corresponding channel

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

Final treatmentpushRouteandpopRouteThe logic ofWidgetsBindingClass, 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();
  }

This code indicates that only the called method returnstrueThe specific processing logic of each handle function is through aWidgetsBindingObserverTo achieve, continue to follow up to find the following code

class _WidgetsAppState extends State<WidgetsApp> with WidgetsBindingObserver {

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

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

handlePopRouteFunction, if there is no oneobserverreturntrueIs calledSystemNavigator.pop();To exit the application

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

FlutterView

FlutterViewThere are not too many functions, mainly two points:

  • Pass in on initializationFlutterViewEngineDelegate
  • establishflutter::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

takeScreenshot:asBase64Encoded:Should beFlutterViewData source for rendering, please refer todrawLayer:inContext:Source code of

@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

We’ll see that laterFlutterViewEngineDelegateActually, it wasFlutterEngineYes.

It’s not right hereIOSSurfaceToo much parsing is based on three layers. You can choose which rendering method to use at compile time

  • If it is a simulator, use the normal calayer
  • In the case of metal rendering, cametallayer is used
  • In the case of opengl rendering, caeagllayer is used
+ (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
}

staycreateSurfaceFunction is mainly to create three correspondingIOSSurface

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

The next rendering is actually handed over toFlutterEngineSelf.

FlutterEngine

FlutterEngineThere are not many interfaces exposed to the outside world, but only a few

  • Constructor,initWithName:project:allowHeadlessExecution,allowHeadlessExecutionAllow no strong dependency when initializing the engineFlutterViewController`
  • Start the engine,runWithEntrypoint:libraryURI:Customizableentrypoint
  • Releasing resources,destroyContext
  • Whether the semantic tree is established or not,ensureSemanticsEnabledThere are few documents about semantic tree, which are probably needed in disabled mode
  • FlutterViewControllerGet / set
  • Finally, there’s a bunch of built-in channels

We are mainly concerned with the construction, start-up, release andFlutterViewControllerThat’s about it,FlutterTextureRegistry, FlutterPluginRegistryNot in 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

Construction of flutterengine

FlutterEngineIn the construction, we should pay attention to the following two points:

  • FlutterDartProjectinitialization
  • FlutterPlatformViewsControllerInitialization of
- (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;
}

Start up of flutterengine

FlutterEngineThe following categories should be paid attention to:

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

FlutterEngineThere are two main things to start

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

  return _shell != nullptr;
}

createShell

createShellThe source code is more, and it is simplified as follows:

  • initializationMessageLoop
  • initializationThreadHost
  • set upon_create_platform_viewCallback
  • set upon_create_rasterizerCallback
  • initializationflutter::TaskRunners, if onembedded_views_previewThe current thread’sTaskRunnerAs a GPU threadTaskRunner
  • establishshellFinally, isolate is started
  • establishFlutterPlatformViewsController
  • establishFlutterObservatoryPublisher
  • set upPlatformView 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;
}

Here you can see that four will be activatedTaskRunner, which are platform, GPU, UI, IO, respectively, but they do not necessarily correspond to four threads.

launchEngine

launchEngineActually, it’s still thereshellOperations on

- (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]);
}
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);
          }));
}

If you follow it up, you will end up in [shell > common > engine.cc ]InsiderunFunction, the core is this line of codePrepareAndLaunchIsolateIn the end, the whole process runs down to start 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;
}

DartIsolate

YesPrepareAndLaunchIsolateThe 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;
}

Let’s take a look at itRunFromLibraryWhat did you do

  • Find entrypoint
  • Call the function of entrypoint,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;
}

I want to see othersInvokeMainEntrypointWhat has been done? The source code has been simplified. We can find the corresponding function in dart end

  • _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;
}

The next step is the tonic library, which is the Library under fuchsia. Google does not open source it to GitHub, but you can see Google source here. Later, we will sort out the tonic Library in other articles.

summary

Flutter runs on IOS. From the source code level, it has the following achievements:

  • It reuses the existing three types of calayer to draw the interface,drawLayerIs calledtakeScreenshotTo obtain the raster image of the fluent interface
  • The corresponding semantic tree will not be built on the native side, and additional generation is required
  • Flutter itself will run in a completely independent thread environment. We need to pay attention to the following four aspectsTaskRunnerPlatform taskrunner is not necessarily a separate thread
  • Platform taskrunner, all interaction between native end and fluent will be processed in platform taskrunner
  • The dart end can pass through thenativeKeyword calls the C / C + + function to get the basic type of data return value, and the performance is better than channel
  • FlutterViewControllerForward all gesture interactions related to them to the flutterengine

Flow chart of fluent

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

  • Looking for dartlibrary
  • Locate entrypoint
  • establishFlutterEngine, pass in dartlibrary and entrypoint
  • establishFlutterViewControllerFlutterView
  • set upFlutterEngineOfviewController
  • Create a shell and start dart VM
  • Load dartlibrary and run DART’s entrypoint
  • Intercept the interface of dart UI, rasterize it and draw calayer

Author’s other articles

How to introduce flutter into existing applications seamlessly?
Practice of flutter’s innovative business at B end of halo travel