布景
跟着Flutter的开展,越来越多的App开始运用Flutter。然后具有一定规划的App会依赖自己保护的基础库,那么运用Flutter重新开发App就会有较高的成本和危险,所以大部分Native App选用渐进式方式引进Flutter,在原有工程的基础上嵌入flutter的才能,由此产生了原生页面和Flutter页面共存的的情况,如何办理路由?官方并没有供给很好的处理计划,于是闲鱼推出了Flutter_Boost。
单引擎&多引擎形式
FlutterBoost 是选用单Engine的计划,所以在介绍原理之前,先搞清楚闲鱼为什么选用单engine的方式。
在一个进程里边最多只会初始化一个Dart VM。然而一个进程能够有多个Flutter Engine,多个Engine实例同享同一个Dart VM,一个Dart VM 能够对应多个isolate。
比如在iOS上面每初始化一个FlutterViewController就会有一个引擎随之初始化,也就意味着会有新的线程去跑Dart代码。假如你发动多个引擎实例,只是不同Engine实例加载的代码跑在各自独立的Isolate。
官方在Flutter 2.0 供给了FlutterEngineGroup, 选用多Engine计划,每个页面是一个Engine,或许一个页面内包括多个Engine,每个Engine对应一个Isolate,内存不同享。从FlutterEngineGroup生成的FlutterEngine ,内存只添加180k。因为它对常用资源进行同享(例如 GPU 上下文、字体衡量和阻隔线程的快照),加速初次烘托的速度、下降推迟并下降内存占用。
例如以下导航操作,完全能够运用官方供给的计划,每个flutter 页面对应一个FlutterViewController/Activity, 并且便利办理。
Flutter Page1 -> Flutter Page2 -> Native Page1 -> Flutter Page3
多引擎形式存在的问题
- 加载资源的冗余,多引擎形式下,不同的engine保护各自的图片/文件缓存,对于资源的重复占用内存,加大了内存压力
- 插件注册以及同原生通讯紊乱,flutter经过message 与原生完成音讯传递,多引擎会构成插件注册以及插件和原生的channel 构成错落
- 添加了页面之间的传参的杂乱,多引擎计划会产生多个isolate,会导致页面传参会愈加杂乱。
归纳多方面考虑,闲鱼选用了单引擎的的计划
开展
Flutter_Boost 作为开源的混合栈计划 经过不断的迭代优化,被越来越多的App 选用,由0.X版别 晋级到到 最新的 4.2版别,适配了null-safe、flutter 最新版别,处理了 黑/白屏、生命周期不一致、crash、freeze 等问题,支持不同场景下运用。
目前我们运用的flutter_boost 版别 release v3.0-null-safety-release.2, 依据此版别对boost 做剖析。
架构
FlutterBoost插件分为渠道和Dart两头,中心经过Message Channel衔接。渠道侧供给了Flutter引擎的装备和办理、Native容器的创立/毁掉、页面可见性改变告诉,以及Flutter页面的翻开/关闭接口等。而Dart侧除了供给相似原生Navigator的页面导航接口的才能外,还负责Flutter页面的路由办理。

原生侧
flutter_boost:引擎的发动,初始化,监听App 生命周期(切换前后台)
FlutterBoostPlugin:负责boost flugin 注册,message 音讯的注册以及处理 , 办理container页面生命周期
FBFlutterContainerManager: 运用Map办理容器
FBFlutterViewContainer:flutterView 容器,将页面生命周期经过FlutterBoostPlugin 传递给dart侧
messages:供给 与flutter端 接受/发送 音讯 的 API
Dart侧
boost_navigator,供给 push pop 等API 办理混合栈,供给PageInfo
flutter_boost_app: 设置注册路由,container的办理,App的构建入口
boost_container:是原生container的抽象类,供给widget 容纳page以及 办理page 页面
container_overlay:办理contaier的pages, 经过可办理的栈 完成pages 层级办理,与contaier 一一对应
boost_flutter_binding:hook flutter page的生命周期
boost_lifecycle_binding:呼应原生侧container生命周期
messages:供给与Native端接受/发送 音讯 FlutterRouterApi/NativeRouterApi
从boost架构图大约能了解dart侧 和native侧 构成,dart和native侧经过音讯传递, dart将push pop 等操作传递给native,native将的contaienr生命周期传递给dart,dart产生呼应。两头相互协作完成了页面办理的才能。
流程梳理&源码剖析
1 boost发动流程
- 首先初始化flutter engine
- 经过[engine runWithEntrypoint],创立isolate,运行包括main()函数的dart 程序
- 依据装备项预热引擎
- 注册一切plugin和音讯channel,并获取到boost插件实力传入delegate
- 监听App切换前后台
- (void)setup:(UIApplication*)application delegate:(id<FlutterBoostDelegate>)delegate callback:(void (^)(FlutterEngine *engine))callback options:(FlutterBoostSetupOptions*)options{
if([delegate respondsToSelector: @selector(engine)]){
self.engine = delegate.engine;
}else{
self.engine = [[FlutterEngine alloc ] initWithName:@"io.flutter" project:options.dartObject];
}
//从options中获取参数
NSString* initialRoute = options.initalRoute;
NSString* dartEntrypointFunctionName = options.dartEntryPoint;
void(^engineRun)(void) = ^(void) {
[self.engine runWithEntrypoint:dartEntrypointFunctionName initialRoute : initialRoute];
//依据装备提早预热引擎,装备默许预热引擎
if(options.warmUpEngine){
[self warmUpEngine];
}
Class clazz = NSClassFromString(@"GeneratedPluginRegistrant");
SEL selector = NSSelectorFromString(@"registerWithRegistry:");
if (clazz && selector && self.engine) {
if ([clazz respondsToSelector:selector]) {
((void (*)(id, SEL, NSObject<FlutterPluginRegistry>*registry))[clazz methodForSelector:selector])(clazz, selector, self.engine);
}
}
self.plugin= [FlutterBoostPlugin getPlugin:self.engine];
self.plugin.delegate=delegate;
if(callback){
callback(self.engine);
}
};
if ([NSThread isMainThread]){
engineRun();
}else{
dispatch_async(dispatch_get_main_queue(), ^{
engineRun();
});
}
[[NSNotificationCenter defaultCenter] addObserver:self selector: @selector(applicationWillEnterForeground:) name:UIApplicationWillEnterForegroundNotificationobject:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector: @selector(applicationDidEnterBackground:) name:UIApplicationDidEnterBackgroundNotificationobject:nil];
}
2 Container 创立与周期同步
- App 发动后会创立FBFlutterViewContainer
- 将创立的contaier 以key-value的形式存储在containerManager
- Flutter 引擎将flutter页面attach到contaier,viewWillAppear时会履行pushRoute并同步contaier的生命周期到dart侧
- (void)viewWillAppear:(BOOL)animated
{
[FB_PLUGIN containerWillAppear:self];
...
}
- (void)viewDidDisappear:(BOOL)animated
{
...
[FB_PLUGIN containerDisappeared:self];
}
- (void)containerWillAppear:(id<FBFlutterContainer>)vc {
....
//显现履行push操作
[self.flutterApi pushRoute: params completion:^(NSError * e) {
}];
[self.containerManager activeContainer:vc forUniqueId:vc.uniqueIDString];
}
// attach
- (void)attatchFlutterEngine
{
if(ENGINE.viewController != self){
ENGINE.viewController=self;
}
}
- 终究dart呼应pushRoute音讯并履行FlutterBoostApp#pushContainer,生成container 以及page 压入栈顶 对应生成overlay entry overlayState.insert(entry) 显现flutter 页面
void pushContainer(String? pageName,
{String? uniqueId,
bool isFromHost = false,
Map<String, dynamic>? arguments}) {
......
final container = _createContainer(pageInfo); // 生成container 和 page
final previousContainer = topContainer;
containers.add(container);
BoostLifecycleBinding.instance
.containerDidPush(container, previousContainer);
// Add a new overlay entry with this container
refreshOnPush(container);
}
BoostContainer({this.key, required this.pageInfo}) {
_pages.add(BoostPage.create(pageInfo));
}
// 页面显现
overlayState.insert(entry);
3 boost dart侧 发动流程
1 FlutterBoostApp 初始化
Native boost 发动时会触发dart main(), 履行 FlutterBoostApp 构建时会传入appBuilder 和routeFactory, routeFactory 是需求跳转的flutter 页面信息,经过此装备判断页面是否为flutter页面,FlutterBoostApp 构建会初始化页面办理,routerAPI, 拦截器,message 注册等
FlutterBoostApp(
routeFactory,
appBuilder: appBuilder,
initialRoute: "initialRoute",
);
2 双侧路由栈页面办理
上文的boost 架构也介绍过boost 在dart 侧运用双层路由栈来办理页面,双路由栈代码如下
双层路由栈结构代码
外部路由
List<BoostContainer> get containers => _containers;
final List<BoostContainer> _containers = <BoostContainer>[];
内部路由
/// A list of page in this container
final List<BoostPage<dynamic>> _pages = <BoostPage<dynamic>>[];
_containers 会存储BoostContainer,而每一个BoostContainer 都会保护_pages 并结合overlayEntry 办理pages
3 Dart侧调用Push&Pop流程
1 当dart侧履行push操作时,withContainer == false 时,终究会生成page 并添加到contaier的pages,终究显现flutter页面, 可参考4
topContainer!.addPage(BoostPage.create(pageInfo))
2 当push 操作 withContainer == true 时分,dart侧终究会经过messageChannel 调用Native 侧Api,假如是flutter页面则会触发 pushFlutterRoute ,生成新的container 并重复走 Container 创立与周期同步的 3 和 4 步骤,将container 存储到containerManager 中,然后在dart侧创立contaier 和 page 并压入双侧栈,终究显现Flutter页面;
- (void)pushFlutterRoute:(FlutterBoostRouteOptions *)options {
FBFlutterViewContainer *vc = FBFlutterViewContainer.new;
[vc setName:options.pageName uniqueId:options.uniqueId params:options.arguments opaque:options.opaque];
...
Push/Present VC
}
假如push Native 页面,则终究会调用一下办法,终究跳转到原生页面
- (void) pushNativeRoute:(NSString *) pageName arguments:(NSDictionary *) arguments {
UIViewController *vc = [[UIViewController alloc] init]:
...
Push/Present VC
}

Pop 操作流程如下


4 页面周期办理
每当push操作withContainer 就会生成新的FlutterViewController, engin 就会attach 到最新的FlutterViewController 上 并烘托显现出页面。Native 和 dart 侧都会存在对应的container, 如图示

页面生命周期
Native 侧生命周期 FLBViewController会同步 生命周期会同步到dart侧,当Native容器展现的时分宣布onContainerShow事情告诉对应的BoostContainer展现,Native容器隐藏时再宣布onContainerHide事情告诉对应的的BoostContainer隐藏。
Dart侧生命周期,dart侧经过 hook handleAppLifecycleStateChanged办法 然后获取flutter页面的生命周期。
别的还供给了一个changeAppLifecycleState办法,这个办法能够真正来改变上层Flutter运用的生命周期,目前这个办法的调用机遇与Flutter容器个数相关,当容器大于等于1,Flutter运用的生命周期状况为resumed,而容器个数为0时,Flutter运用的生命周期状况则为paused。
这样就做到不侵略flutter的引擎的一起获取到flutter页面的生命周期, Native自己办理生命周期时间,并在适当机遇给Dart侧发送appIsResumed音讯处理引擎复用时生命周期事情紊乱导致的页面卡死问题
mixin BoostFlutterBinding on WidgetsFlutterBinding {
bool _appLifecycleStateLocked = true;
@override
void initInstances() {
super.initInstances();
_instance = this;
changeAppLifecycleState(AppLifecycleState.resumed);
}
static BoostFlutterBinding get instance => _instance;
static BoostFlutterBinding _instance;
@override
void handleAppLifecycleStateChanged(AppLifecycleState state) {
if (_appLifecycleStateLocked) {
return;
}
Logger.log('boost_flutter_binding: handleAppLifecycleStateChanged ${state.toString()}');
super.handleAppLifecycleStateChanged(state);
}
void changeAppLifecycleState(AppLifecycleState state) {
if (SchedulerBinding.instance.lifecycleState == state) {
return;
}
_appLifecycleStateLocked = false;
handleAppLifecycleStateChanged(state);
_appLifecycleStateLocked = true;
}
}
总结
以上总结了flutter_boost在单engine形式下的页面办理、发动和履行流程等等,大约能了解boost的原理,flutter_boost 好比将App变成一个浏览器,能够再当前页面切换不同网页,也能够新建页面切换网页,Native 和dart 侧 相互协作,构成终究的页面办理才能。
flutter_boost许多细节点值得关注,比如运用双链表确保履行时序,热更新时两头栈的同步,以及原生侧经过category暴露flutterVC的私有办法,有许多细节点值得我们学习和深究,
未经作者允许,制止转载
如有纰漏,期望大家指正。