本文为 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 { auto screenshot = [_delegate takeScreenshot:flutter::Rasterizer::ScreenshotType::UncompressedImage asBase64Encoded:NO ]; 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 <CGImageRef > image(CGImageCreate (...)); 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 { if (!project) { project = [[[FlutterDartProject alloc] init] autorelease]; } auto engine = fml::scoped_nsobject<FlutterEngine>{[[FlutterEngine alloc] initWithName:@"io.flutter" project:project allowHeadlessExecution:self .engineAllowHeadlessExecution]}; _flutterView.reset([[FlutterView alloc] initWithDelegate:_engine opaque:self .isViewOpaque]); [_engine.get() createShell:nil libraryURI:nil initialRoute:initialRoute]; }
可以看到,FlutterViewController
并不是只负责管理 FlutterView
,同时也会持有 FlutterEngine
和 FlutterDartProject
,方便各个模块的调用。这也非常符合 iOS 中 UIViewController 的角色定义😄。
同时我们注意到, _flutterView 在 FlutterViewController
初始化的时候就创建了,并不是在 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 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 - (instancetype )initWithName:(NSString *)labelPrefix project:(nullable FlutterDartProject*)project;
FlutterEngine
初始化时需要传入一个 labelPrefix 参数,作为 FlutterEngine
内部的线程名的前缀,方便和其他线程进行区分。 同时还需要传入一个 FlutterDartProject
对象,其作用后面会再详细说明,此处暂且略过。
FlutterEngine
主要做了两件事情:
调用 createShell:libraryURI:initialRoute 方法,创建 shell 并进行一系列初始化。
调用 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 { 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()); }; flutter::Shell::CreateCallback<flutter::Rasterizer> on_create_rasterizer = [](flutter::Shell& shell) { return std::make_unique<flutter::Rasterizer>(shell); }; flutter::TaskRunners task_runners(threadLabel.UTF8String, fml::MessageLoop::GetCurrent().GetTaskRunner(), _threadHost->raster_thread->GetTaskRunner(), _threadHost->ui_thread->GetTaskRunner(), _threadHost->io_thread->GetTaskRunner() ); std::unique_ptr<flutter::Shell> shell = flutter::Shell::Create(std::move(task_runners), std::move(platformData), std::move(settings), on_create_platform_view, on_create_rasterizer ); 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,分配负责 platform 、 raster 、 ui 、 io
创建 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 { 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); }
再回头看 FlutterView
的 drawLayer: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 { auto screenshot = [_delegate takeScreenshot:flutter::Rasterizer::ScreenshotType::UncompressedImage asBase64Encoded:NO ]; } ## FlutterProject `FlutterViewController`、`FlutterView`、`FlutterEngine` 这三者介绍地差不多了,最后再来看一下 `FlutterDartProject`。 `FlutterDartProject` 负责管理 Flutter 资源,创建 `FlutterEngine` 实例等。 `FlutterDartProject` 代码量也不多,功能也比较简单,主要提供一个初始化方法,和一系列的 *lookupKeyForAsset* 方法。 ```objc @interface FlutterDartProject : NSObject - (instancetype )initWithPrecompiledDartBundle:(nullable NSBundle *)bundle NS_DESIGNATED_INITIALIZER ; + (NSString *)lookupKeyForAsset:(NSString *)asset; @end
FlutterDartProject
的代码逻辑虽然简单,但是它的初始化优先级很高。在 FlutterViewController
和 FlutterEngine
的初始化方法中,都需要传入 FlutterDartProject
对象。
总结 Dart UI 在 iOS APP 中的展示流程,主要涉及 FlutterViewController
、FlutterView
、FlutterEngine
和 FlutterDartProject
等几个类或模块,在 Dart UI 的展示中,它们各自的主要作用为:
FlutterView
展示UI
FlutterViewController
管理FlutterView等对象
FlutterEngine
获取 Dart UI 的界面并进行光栅化
FlutterDartProject
管理Flutter资源