深入解析:我们项目的 Flutter 引擎管理与通信机制
一、Flutter 引擎管理架构
我们的项目采用了 Flutter 2.0+ 官方推荐的多引擎复用模式,核心目标是实现轻量级的混合开发体验。
1.1 核心管理类:BKRunnerShared
引擎管理的中枢是位于 flutter_runner 库中的 BKRunnerShared 单例。它负责协调引擎的创建、复用和插件注册。
-
多引擎组 (
FlutterEngineGroup): 我们并没有维护一个单独的全局单例 Engine,而是维护了一个FlutterEngineGroup实例(名为beike_engine_group)。为什么使用 Group?
FlutterEngineGroup允许创建多个轻量级的FlutterEngine实例。这些实例并非完全隔离,而是共享了极其昂贵的底层资源(如 Skia 上下文、字体管理器、Dart VM 代码段内存映射)。这使得创建新引擎的内存开销降低了约 99%(从 ~20MB 降至 ~180KB),且启动速度只需几毫秒。 -
初始化时机:
BKRunnerShared采用懒加载 (Lazy Loading) 策略。- App 启动时 (
didFinishLaunching):仅配置了插件注册的回调 (configRegisterBlock),不创建引擎,不启动 Dart VM。 - 页面跳转时 (
open router):当业务侧明确需要打开 Flutter 页面时,通过BKFlutterPlatformRouterImp触发引擎创建流程。
- App 启动时 (
1.2 引擎创建流程
当用户点击一个按钮跳转 Flutter 页面时,发生了以下步骤:
-
资源完整性检查 (Resource Check) 调用
[[LJBeikeFlutterManager sharedManager] checkProductData]。- Thin Mode(瘦身模式)支持:我们的 Flutter 产物(
assets,web_snapshot等)被从主二进制剥离,打包为flutter_resource.zip。 - 如果本地资源版本不一致或缺失,会显示 Loading 提示,并在后台线程解压资源。
- Thin Mode(瘦身模式)支持:我们的 Flutter 产物(
-
获取/创建引擎 (Request Engine) 调用
[[BKRunnerShared sharedInstance] getNewEngineEntryPoint:...]。- 内部调用
_engineGroup makeEngineWithOptions:创建一个新的轻量级 Engine。 - 插件注册:立即执行预设的
pluginRegisterBlock,为这个新引擎注册所有原生插件(如OneNotificationPlugin等)。
- 内部调用
-
容器绑定 将新创建的 Engine 注入到
BKFlutterViewController中,推入导航栈显示。
二、通信机制:OneNotification
项目使用自研的 OneNotification 框架实现 Native 与 Flutter 的全双工通信。
2.1 架构设计
OneNotification 是一个基于 MethodChannel 的三层架构方案:
Layer 1: 业务 API (Observer Mode)
Layer 2: 桥接层 (NSNotificationCenter)
Layer 3: 传输层 (MethodChannel)
2.2 通信流程
-
Flutter → iOS: Flutter 调用
OneNotification.post()->MethodChannel传递给 Native Plugin -> Plugin 通过NSNotificationCenter分发给 iOS 原生业务模块。 -
iOS → Flutter: iOS 业务调用
[[OneNotification shared] post:...]->NSNotificationCenter通知 Plugin -> Plugin 通过MethodChannel调用 Flutter 端方法 -> Flutter 触发订阅回调。 -
多引擎同步: 为了支持多引擎场景,
OneNotificationPlugin内部维护了一个 Plugin 实例的弱引用表。当从 Native 发送通知时,会遍历所有活跃的 Plugin(即所有活跃的 Flutter 页面),确保所有页面都能收到通知。
三、内存与生命周期
关于大家关心的 “打开一次 Flutter 页面是否永久占用内存” 的问题,答案是:大部分释放,少量常驻。
3.1 引擎生命周期
- 创建:打开页面时即时创建。
- 销毁:关闭页面(ViewController dealloc)时,对应的
FlutterEngine实例及该页面内的 Dart 堆内存(变量、Widget 树)会被完全销毁。我们没有缓存具体的 Engine 实例。
3.2 常驻内存(Group Overhead)
- 一旦
FlutterEngineGroup初始化(即打开过一次 Flutter 页面),为了保证后续页面的”秒开”体验,它持有的共享底层资源(VM Snapshot Mapping, Skia Cache)会常驻内存。 - 这部分开销是固定的(Foundational Cost),不会随着页面开关次数累积。
总结
我们的管理机制在启动速度、内存占用和开发灵活性之间取得了很好的平衡:
- 无预热:不拖慢 App 启动速度。
- 多引擎 + Group:解决状态污染问题,同时保持低内存开销。
- 瘦身模式:减小安装包体积,支持动态更新。