文章目录
摘要
本文主要是针对 Flutter 在 iOS 上是如何运行起来的源码进行串联,总结大致的运行流程。
涉及到的关键类有以下几个:
- FlutterViewController
- FlutterView
- FlutterEngine
- DartIsolate
FlutterViewController
Flutter 嵌入原生应用必须有个载体,从这个点入手,在 Flutter Engine 源码中的 API 的入口点是 FlutterViewController
,对其头文件源码做精简,大致如下
@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
FlutterViewController 的构造函数
FlutterViewController 有两个构造函数,本质上是一样的,第一个构造函数是谷歌为了在存在多个 FlutterViewController
的场景下为了让使用者能复用 FlutterEngine
而开放的。
- (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;
}
在构造函数中主要做了这么几件事情:
-
初始化或者替换当前的
FlutterEngine
-
初始化
FlutterView
-
初始化正在发生的手势集合
-
加载闪屏页,传入
FlutterEngine
的构造函数没有这项,应该是考虑了多FlutterViewController
的场景下不好频繁加载闪屏页 -
设置
UIInterfaceOrientationMask
和UIStatusBarStyle
-
添加一系列的通知,包括
Application
的生命周期,键盘事件,Accessibility
的事件等 -
将
FlutterViewController
设置给FlutterEngine
第二个构造函数中还多了这行代码,第一个构造函数把这个调用延后了而已
[_engine.get() createShell:nil libraryURI:nil];
FlutterViewController 的 loadView
在 loadView
函数中,设置了 FlutterViewController
的 view
,并判断是否需要加载闪屏页,可以通过重写 splashScreenView
的 get 方法返回 nil
的方式彻底不加载闪屏页
- (void)loadView {
self.view = _flutterView.get();
self.view.multipleTouchEnabled = YES;
self.view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
[self installSplashScreenViewIfNecessary];
}
FlutterViewController 对 Navigator 的操作
FlutterViewController
提供了三个接口允许我们在原生端对 dart 的 Navigator
直接进行操作
- (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
setInitialRoute
在 iOS 端通过 navigationChannel
来告诉 dart 具体的 initialRoute,这个过程略微特殊,并不会在 dart 端直接接收 channel 信息,
而是在引擎层面做了处理,web_ui 不在本文的解析范畴,这里直接洗跟原生相关的点
setInitialRoute
设置流程如下:
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_->