0%

Flutter 系列之 iOS APP 构建流程(二)

本文为 Flutter 学习系列文章的第二篇,主要介绍 Dart UI 在 iOS APP 的展示流程。

Flutter iOS Framework 源码地址: https://github.com/flutter/engine/tree/master/shell/platform/darwin/ios/framework/Source, commit 为 b0f61e109683be2853819b0e55251867febefc34 。

为了保证阅读效果,本文的代码片段均为并不是完整的源代码,仅挑出了重点的部分并添加相应注释。

前言

本文主要通过介绍以下 4 个类的作用及关系,讲述 Dart UI 在 iOS APP 的展示流程。

  • FlutterView
  • FlutterViewController
  • FlutterEngine
  • FlutterDartProject

FlutterView

FlutterView 继承自 UIView,负责视图的展示。
FlutterView 初始化时,需要传入一个 FlutterViewEngineDelegate 对象,其实就是 FlutterEngine

1
2
3
4
5
@interface FlutterView : UIView

- (instancetype)initWithDelegate:(id<FlutterViewEngineDelegate>)delegate opaque:(BOOL)opaque;

@end

FlutterView 的渲染逻辑,会交由 FlutterEngine 处理。 FlutterEngine 通过 takeScreenshot 方法获取渲染数据,最终调用 iOS 系统提供的 CGContextDrawImage 方法完成绘制。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
- (void)drawLayer:(CALayer*)layer inContext:(CGContextRef)context {
// 创建screenshot,获取渲染数据源,并生成NSData对象
auto screenshot = [_delegate takeScreenshot:flutter::Rasterizer::ScreenshotType::UncompressedImage
asBase64Encoded:NO];
NSData* data = [NSData dataWithBytes:const_cast<void*>(screenshot.data->data())
length:screenshot.data->size()];

// 通过NSDate创建CGImage对象
fml::CFRef<CGDataProviderRef> image_data_provider(
CGDataProviderCreateWithCFData(reinterpret_cast<CFDataRef>(data)));
fml::CFRef<CGImageRef> image(CGImageCreate(...));

// 绘制image
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);
}

FlutterView 的代码量很少,主要是重写了 drawLayer:inContext: 方法以便执行Flutter的绘制逻辑。

FlutterViewController

在 iOS 开发中,只有 UIView 肯定是不行的,还需要有一个 UIViewController 来承载和管理 UIView 。
FlutterViewController 就是这样的一个 UIViewController 。

1
2
3
4
5
6

@interface FlutterViewController : UIViewController

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

FlutterViewController 在创建时需要传入一个 FlutterDartProject 对象;如果传入为 nil 的话,那么 FlutterViewController 初始化的第一件事就是创建 FlutterDartProject 对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
- (void)sharedSetupWithProject:(nullable FlutterDartProject*)project
initialRoute:(nullable NSString*)initialRoute {
// 保证在创建FlutterEngine之前,已经有了FlutterDartProject对象
if (!project) {
project = [[[FlutterDartProject alloc] init] autorelease];
}
// 创建FlutterEngine对象
auto engine = fml::scoped_nsobject<FlutterEngine>{[[FlutterEngine alloc]
initWithName:@"io.flutter"
project:project
allowHeadlessExecution:self.engineAllowHeadlessExecution]};
// 创建新的FlutterView
_flutterView.reset([[FlutterView alloc] initWithDelegate:_engine opaque:self.isViewOpaque]);
[_engine.get() createShell:nil libraryURI:nil initialRoute:initialRoute];
}

可以看到,FlutterViewController 并不是只负责管理 FlutterView,同时也会持有 FlutterEngineFlutterDartProject,方便各个模块的调用。这也非常符合 iOS 中 UIViewController 的角色定义😄。

同时我们注意到, _flutterViewFlutterViewController 初始化的时候就创建了,并不是在 loadView 方法中创建的。那 _flutterView 是什么时候变成 FlutterViewController.view 的呢? 我们看下 loadView 方法的实现,就一目了然了。

1
2
3
4
- (void)loadView {
self.view = GetViewOrPlaceholder(_flutterView.get());
// ...
}

此外,FlutterViewController 还负责管理启动页、处理APP生命周期事件、处理触摸及键盘事件等。

FlutterDartEngine

FlutterEngine 负责承载具体的渲染逻辑,通过 shell 不断获取 Dart UI snapshot 并调用系统绘制接口,完成 Dart UI 的展示。

上文提到,FlutterView 初始化时,需要传入一个 FlutterViewEngineDelegate 对象,也就是FlutterEngine

FlutterViewController 的初始化方法中,我们也看到了 FlutterEngine 的初始化代码。

1
2
3
4
5
6
// 创建FlutterEngine对象
auto engine = fml::scoped_nsobject<FlutterEngine>{[[FlutterEngine alloc]
initWithName:@"io.flutter"
project:project
allowHeadlessExecution:self.engineAllowHeadlessExecution]};
[_engine.get() createShell:nil libraryURI:nil initialRoute:initialRoute];
1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* Initialize this FlutterEngine with a `FlutterDartProject`.
*
* If the FlutterDartProject is not specified, the FlutterEngine will attempt to locate
* the project in a default location (the flutter_assets folder in the iOS application
* bundle).
*
* @param labelPrefix The label prefix used to identify threads for this instance. Should
* be unique across FlutterEngine instances, and is used in instrumentation to label
* the threads used by this FlutterEngine.
* @param project The `FlutterDartProject` to run.
*/
- (instancetype)initWithName:(NSString*)labelPrefix project:(nullable FlutterDartProject*)project;

FlutterEngine 初始化时需要传入一个 labelPrefix 参数,作为 FlutterEngine 内部的线程名的前缀,方便和其他线程进行区分。
同时还需要传入一个 FlutterDartProject 对象,其作用后面会再详细说明,此处暂且略过。

FlutterEngine 主要做了两件事情:

  1. 调用 createShell:libraryURI:initialRoute 方法,创建 shell 并进行一系列初始化。
  2. 调用 launchEngine:libraryURI: 方法,启动 engine

createShell

shell 可以理解为将 Dart 执行环境 和 Native 执行环境分离开来的 “壳”。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
- (BOOL)createShell:(NSString*)entrypoint
libraryURI:(NSString*)libraryURI
initialRoute:(NSString*)initialRoute {
// ...
// 设置 on_create_platform_view 回调
flutter::Shell::CreateCallback<flutter::PlatformView> on_create_platform_view =
[self](flutter::Shell& shell) {
[self recreatePlatformViewController];
return std::make_unique<flutter::PlatformViewIOS>(
shell, self->_renderingApi, self->_platformViewsController, shell.GetTaskRunners());
};
// 设置 on_create_rasterizer 回调
flutter::Shell::CreateCallback<flutter::Rasterizer> on_create_rasterizer =
[](flutter::Shell& shell) { return std::make_unique<flutter::Rasterizer>(shell); };
// 初始化 TaskRunners
flutter::TaskRunners task_runners(threadLabel.UTF8String, // label
fml::MessageLoop::GetCurrent().GetTaskRunner(), // platform
_threadHost->raster_thread->GetTaskRunner(), // raster
_threadHost->ui_thread->GetTaskRunner(), // ui
_threadHost->io_thread->GetTaskRunner() // io
);

std::unique_ptr<flutter::Shell> shell =
flutter::Shell::Create(std::move(task_runners), // task runners
std::move(platformData), // window data
std::move(settings), // settings
on_create_platform_view, // platform view creation
on_create_rasterizer // rasterzier creation
);

if (shell == nullptr) {
FML_LOG(ERROR) << "Could not start a shell FlutterEngine with entrypoint: "
<< entrypoint.UTF8String;
} else {
[self setupShell:std::move(shell)
withObservatoryPublication:settings.enable_observatory_publication];
}
return _shell != nullptr;
}

- (void)setupShell:(std::unique_ptr<flutter::Shell>)shell
withObservatoryPublication:(BOOL)doesObservatoryPublication {
// ...
[self setupChannels];
_publisher.reset([[FlutterObservatoryPublisher alloc]
initWithEnableObservatoryPublication:doesObservatoryPublication]);
[self maybeSetupPlatformViewChannels];
// ...
}

createShell 这一步主要做了以下几件事情

  • 设置 on_create_platform_view 回调
  • 设置 on_create_rasterizer 回调
  • 初始化 TaskRunners,分配负责 platformrasteruiio
  • 创建 shell,最终会启动 Isolate
  • 创建 FlutterPlatformViewsController
  • 创建 FlutterObservatoryPublisher
  • 创建 PlatformView Channels

launchEngine

launchEngine 方法需要传入一个 entrypoint*,其实就是传入 Dart 代码的执行入口,进而运行 Dart 代码
这一步直接调用创建好的 *shell
RunEngine 方法,基于 FlutterDartProject 配置信息启动 FlutterEngine

1
2
3
4
- (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]);

启动 FLutterEngine 之后,FlutterEngine 不断通过 takeScreenshot 方法截取 Dart UI 的界面并进行光栅化,最后绘制 CALayer。

1
2
3
4
5
- (flutter::Rasterizer::Screenshot)takeScreenshot:(flutter::Rasterizer::ScreenshotType)type
asBase64Encoded:(BOOL)base64Encode {
FML_DCHECK(_shell) << "Cannot takeScreenshot without a shell";
return _shell->Screenshot(type, base64Encode);
}

再回头看 FlutterViewdrawLayer:inContext: 方法,Dart UI 的展示流程就很清晰了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
- (void)drawLayer:(CALayer*)layer inContext:(CGContextRef)context {
// 创建screenshot,获取渲染数据源,并生成NSData对象
auto screenshot = [_delegate takeScreenshot:flutter::Rasterizer::ScreenshotType::UncompressedImage
asBase64Encoded:NO];
}

## FlutterProject

`FlutterViewController`、`FlutterView`、`FlutterEngine` 这三者介绍地差不多了,最后再来看一下 `FlutterDartProject`。

`FlutterDartProject` 负责管理 Flutter 资源,创建 `FlutterEngine` 实例等。

`FlutterDartProject` 代码量也不多,功能也比较简单,主要提供一个初始化方法,和一系列的 *lookupKeyForAsset* 方法。

```objc
@interface FlutterDartProject : NSObject
/**
* Initializes a Flutter Dart project from a bundle.
*/
- (instancetype)initWithPrecompiledDartBundle:(nullable NSBundle*)bundle NS_DESIGNATED_INITIALIZER;

/**
* Returns the file name for the given asset. If the bundle with the identifier
* "io.flutter.flutter.app" exists, it will try use that bundle; otherwise, it
* will use the main bundle. To specify a different bundle, use
* `-lookupKeyForAsset:asset:fromBundle`.
*
* @param asset The name of the asset. The name can be hierarchical.
* @return the file name to be used for lookup in the main bundle.
*/
+ (NSString*)lookupKeyForAsset:(NSString*)asset;

@end

FlutterDartProject 的代码逻辑虽然简单,但是它的初始化优先级很高。在 FlutterViewControllerFlutterEngine 的初始化方法中,都需要传入 FlutterDartProject 对象。

总结

Dart UI 在 iOS APP 中的展示流程,主要涉及 FlutterViewControllerFlutterViewFlutterEngineFlutterDartProject 等几个类或模块,在 Dart UI 的展示中,它们各自的主要作用为:

  • FlutterView 展示UI
  • FlutterViewController 管理FlutterView等对象
  • FlutterEngine 获取 Dart UI 的界面并进行光栅化
  • FlutterDartProject 管理Flutter资源