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 initialization
FlutterViewEngineDelegate
- create
flutter::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 dependencies
FlutterViewController` - Start the engine,
runWithEntrypoint:libraryURI:
Custom can be passed inentrypoint
- Freeing up resources,
destroyContext
- Whether the semantic tree is established,
ensureSemanticsEnabled
There is little documentation about semantic trees, probably something you need in disabled mode FlutterViewController
Get/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:
FlutterDartProject
Initialize theFlutterPlatformViewsController
The 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 the
MessageLoop
- Initialize the
ThreadHost
- Set up the
on_create_platform_view
The callback - Set up the
on_create_rasterizer
The callback - Initialize the
flutter::TaskRunners
, if turned onembedded_views_preview
Is used for the current threadTaskRunner
As a GPU threadTaskRunner
- create
shell
And finally start the Isolate - create
FlutterPlatformViewsController
- create
FlutterObservatoryPublisher
- Set up the
PlatformView
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,
drawLayer
Called whentakeScreenshot
To 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 on
TaskRunner
Platform TaskRunner is not necessarily a separate thread - Platform TaskRunner, all native interactions with Flutter are handled by Platform TaskRunner
- The DART end can pass
native
The keyword calls a C/C++ function that retrieves the return value of the primitive type of data and performs better than channel FlutterViewController
All 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
- create
FlutterEngine
, passed to DartLibrary and Entrypoint - create
FlutterViewController
.FlutterView
- Set up the
FlutterEngine
的viewController
- 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?