cubegao

flutter_boost 路由架构设计详解

2025-01-14

一、混合栈的诞生背景

在 Flutter 的早期阶段,它设计的目标是「全 Flutter 应用」——整个 App 的所有页面都由 Flutter 渲染。但现实是大多数企业级 App 是逐步引入 Flutter 的:现存大量原生页面,新需求逐步用 Flutter 开发。这就产生了混合栈问题——Flutter 页面和原生页面需要在同一个导航栈中共存。

例如,在企业 IM 中,一个典型的使用流程是:

1
原生消息列表 → Flutter 聊天页面 → 原生用户详情页 → Flutter 选人组件 → 原生通讯录

Flutter 官方的 Navigator 只能管理 Flutter 内部的页面栈,无法处理 Flutter 和原生页面的混合导航。flutter_boost 就是为解决这个问题而生的。

本文从架构层面解析 flutter_boost 的设计思想,结合企业 IM 的实际使用经验,分析它的路由管理机制、生命周期同步策略和常见踩坑点。

二、flutter_boost 的核心设计

2.1 统一路由管理

flutter_boost 的核心思路是:不再用 Flutter 的 Navigator,而是用原生的 NavigationController 统一管理所有页面的栈

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
┌───────────────────────────────────┐
│ Native Navigator │
│ ┌─────────────────────────────┐ │
│ │ Activity / ViewController │ │
│ ├─────────────────────────────┤ │
│ │ flutter_boost Container │ │
│ │ ┌───────────────────────┐ │ │
│ │ │ Flutter Page A │ │ │
│ │ │ Flutter Page B │ │ │
│ │ │ ... │ │ │
│ │ └───────────────────────┘ │ │
│ └─────────────────────────────┘ │
│ 原生页面 C │
│ ┌─────────────────────────────┐ │
│ │ flutter_boost Container │ │
│ │ ┌───────────────────────┐ │ │
│ │ │ Flutter Page D │ │ │
│ │ └───────────────────────┘ │ │
│ └─────────────────────────────┘ │
└───────────────────────────────────┘

每一个 Flutter 页面都是独立的一个 FlutterEngine(或共享 Engine 中的一个独立 Container),嵌入在对应的原生 Activity/ViewController 中。原生 Navigator 管理原生页面和 Flutter Container 的 push/pop,flutter_boost 在内部管理 Flutter 页面之间的跳转。

2.2 BoostNavigator 的实现

flutter_boost 暴露给 Flutter 侧的 API 是 BoostNavigator

1
2
3
4
5
6
7
8
// Flutter 侧跳转到另一个 Flutter 页面
BoostNavigator.instance.push('chat_page', arguments: {'conversationId': '123'});

// 跳转到原生页面
BoostNavigator.instance.push('native_user_detail', arguments: {'userId': '456'});

// 返回
BoostNavigator.instance.pop();

所有路由操作都通过 MethodChannel 发送到原生侧。原生侧的 FlutterBoostDelegate 负责实际的页面跳转:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// Android 原生侧
class AppBoostDelegate : FlutterBoostDelegate {
override fun pushNativeRoute(url: String, arguments: Map<String, Any>?) {
when (url) {
"native_user_detail" -> {
val intent = Intent(activity, UserDetailActivity::class.java)
intent.putExtra("userId", arguments?.get("userId") as String)
activity.startActivity(intent)
}
}
}

override fun pushFlutterRoute(url: String, arguments: Map<String, Any>?) {
val intent = FlutterBoostActivity.CachedEngineIntentBuilder(DefaultFlutterPage::class.java)
.url(url)
.params(arguments)
.build(activity)
activity.startActivity(intent)
}
}

2.3 多 Engine 与单 Engine 的权衡

flutter_boost 经历了从多 Engine 到单 Engine 的架构演进:

早期版本(多 Engine):每个 Flutter 页面一个独立的 Engine。优点是隔离性好,一个页面崩溃不影响其他页面。缺点是内存开销极大——每个 Engine 占 20-30MB,十个 Flutter 页面就是 200-300MB,在低端设备上直接被系统杀掉。

当前版本(单 Engine + 多 Container):全局共享一个 Engine,每个 Flutter 页面是一个独立的 Container。内存占用大幅降低,但隔离性下降——单个页面的 OOM 可能导致所有 Flutter 页面崩溃。

在企业 IM 中,我们选择了单 Engine 模式。IM 应用中 Flutter 页面数量多(聊天、通讯录、选人、转发、会议等),多 Engine 的内存开销无法接受。但为了降低单点风险,我们在关键路径上增加了内存监控:当 Flutter Engine 的内存超过阈值时,主动销毁重建,避免整体崩溃。

三、生命周期管理的深度解析

3.1 双轨生命周期的挑战

混合栈场景下,Flutter 页面面临两套独立的生命周期:

  • 原生侧:Activity/ViewController 的 onCreate/onResume/onPause/onDestroy
  • Flutter 侧WidgetsBindingObserverdidChangeAppLifecycleState

flutter_boost 需要将原生侧的生命周期事件同步到 Flutter 侧。它的实现方式是通过 LifecycleChannel 在原生生命周期回调中向 Flutter 侧发送事件:

1
2
3
4
5
6
7
8
9
10
11
12
// flutter_boost 内部的 ContainerLifeCycle 管理
class ContainerLifeCycle extends LifeCycle {
void onForeground() {
// 对应原生 onResume
_sendLifecycleEvent(AppLifecycleState.resumed);
}

void onBackground() {
// 对应原生 onPause
_sendLifecycleEvent(AppLifecycleState.paused);
}
}

3.2 常见生命周期问题

问题一:Flutter 页面的 dispose 不可靠

因为 Flutter 页面的销毁由原生侧驱动(原生 Activity finish → flutter_boost 销毁 Container),flutter_boost 的 dispose 回调有延迟。在聊天页面中,如果用户快速返回,WebSocket 连接可能没有及时关闭。

解决方案:在 StatefulWidget.dispose() 中清理资源,同时通过 BoostNavigator.instance.addCloseListener() 兜底:

1
2
3
4
5
6
7
8
9
10
11
class ChatPage extends StatefulWidget {
@override
void initState() {
super.initState();
BoostNavigator.instance.addCloseListener((route) {
if (route.pageInfo.pageName == 'chat_page') {
_cleanupResources();
}
});
}
}

问题二:原生页面覆在 Flutter 页面之上时,Flutter 侧的生命周期不确定

当原生 Dialog 弹出覆盖 Flutter 页面时,不同的 Android 版本和 ROM 对待 Flutter Engine 的行为不一致。部分设备会暂停 Engine 的渲染管线,部分不会。

我们的处理策略是:在弹窗场景下,主动调用 BoostNavigator.instance.pausePage() 暂停 Flutter 页面的动画和定时器,避免不必要的 CPU 消耗。弹窗消失后调用 resumePage() 恢复。

四、路由拦截与权限控制

在企业 IM 中,某些页面的访问需要权限校验。flutter_boost 支持路由拦截:

1
2
3
4
5
6
7
8
9
10
11
12
13
class AppBoostDelegate : FlutterBoostDelegate {
override fun pushFlutterRoute(url: String, arguments: Map<String, Any>?): Boolean {
// 路由拦截
if (!url.startsWith("chat_")) {
if (!UserManager.hasPermission("advanced_feature")) {
Toast.show("无权限访问")
return true // 已拦截,不再跳转
}
}
// 正常跳转
return super.pushFlutterRoute(url, arguments)
}
}

拦截器在原生侧执行的好处是:权限判断逻辑与原生权限体系集成,不依赖 Flutter 侧的初始化时机。

五、性能优化实践

5.1 预热 Engine

flutter_boost 单 Engine 模式下,第一个 Flutter 页面的启动时间包含 Engine 初始化,可能有 200-500ms 的白屏。我们采用 Engine 预热策略:在 App 启动后、用户到达消息列表时,在后台初始化 Engine:

1
2
3
4
// Application.onCreate 中
FlutterBoost.instance.setup(application, delegate) { engine ->
// Engine 初始化完成,已预热
}

预热后,首次打开 Flutter 页面的时间从 400ms 降到 100ms 左右。

5.2 页面预加载

对于高频使用的 Flutter 页面(如聊天页面),我们在消息列表页就提前创建 Container,用户点击后直接切换显示:

1
2
// 消息列表中预创建聊天页面
BoostNavigator.instance.preload('chat_page', arguments: {'conversationId': id});

代价是增加了空闲内存占用。我们设置了预加载上限:最多预加载 2 个最近使用的聊天页面。

5.3 backGestureEnabled 的管理

iOS 的侧滑返回手势在混合栈中容易产生冲突——原生侧和 Flutter 侧都可能响应滑动手势。flutter_boost 提供了 backGestureEnabled 参数控制:

1
2
3
4
5
BoostNavigator.instance.push('detail_page',
withContainer: true,
opaque: false, // 不使用不透明模式,减少内存
arguments: {'backGestureEnabled': false}, // 特定页面禁用侧滑
);

六、踩坑总结

6.1 原生转场动画与 Flutter Hero 动画的冲突

当从原生页面跳转到 Flutter 页面时,如果 Flutter 页面有 Hero 动画,会与原生 Activity 的转场动画产生视觉冲突。解决方案是在 Flutter 侧延迟 Hero 动画的启动,等待原生转场完成:

1
2
3
Future.delayed(Duration(milliseconds: 300), () {
// 原生转场完成后,再执行 Flutter Hero 动画
});

6.2 多 Tab 场景下的状态管理

企业 IM 的消息列表是一个底部多 Tab 结构(消息/通讯录/工作台/我),每个 Tab 都可能包含 Flutter 页面。当用户切换 Tab 时,flutter_boost 可能销毁上一个 Tab 的 Flutter 页面。

解决方案是将多 Tab 的 Flutter 页面放在同一个 Container 中,用 IndexedStack 保持状态:

1
2
3
4
5
6
7
8
9
IndexedStack(
index: currentTabIndex,
children: [
ChatListPage(),
ContactPage(),
WorkplacePage(),
ProfilePage(),
],
)

这样切换 Tab 不会触发页面的 dispose,状态保持。

七、总结

flutter_boost 本质上是一个原生驱动、双向同步的路由适配层:

  • 原生驱动:所有导航由原生 Navigator 统一管理,flutter_boost 是原生发号施令的执行者。
  • 双向同步:生命周期事件、路由事件、返回手势在原生侧和 Flutter 侧之间双向同步。

在企业 IM 的混合栈实践中,flutter_boost 的价值在于:

  1. 无缝混合导航:Flutter 页面和原生页面在同一栈中共存,用户感知不到技术差异。
  2. 渐进迁移支持:不需要一次性将所有页面改为 Flutter,可以逐步替换。
  3. 生命周期可靠同步:App 进入后台、回到前台时,Flutter 页面能正确响应。

但它不是银弹:单 Engine 模式下的内存风险、双轨生命周期的复杂性、路由拦截的性能开销,都需要团队有足够的技术储备来应对。如果你的团队没有混合栈需求(全 Flutter 应用),flutter_boost 是不必要的复杂度。

扫描二维码,分享此文章