深入解析:我们项目的 Flutter 引擎管理与通信机制
一、Flutter 引擎管理架构
我们的项目采用了 Flutter 2.0+ 官方推荐的多引擎复用模式,核心目标是实现轻量级的混合开发体验。
1.1 核心管理类:BKRunnerShared
引擎管理的中枢是位于 flutter_runner 库中的 BKRunnerShared 单例。它负责协调引擎的创建、复用和插件注册。
-
多引擎组 (
FlutterEngineGroup): 我们并没有维护一个单独的全局单例 Engine,而是维护了一个FlutterEngineGroup实例(名为beike_engine_group)。为什么使用 Group?
FlutterEngineGroup允许后续 Engine 复用同一组可共享的 VM/GPU 等底层资源,因此通常比完全独立创建多个 Engine 更快、增量内存更低。但“降低 99%”“180KB”“只需几毫秒”都不是 Flutter API 的跨版本保证,会受到 Flutter 版本、插件、首帧页面、AOT 产物和设备影响,必须在项目中实测。 -
初始化时机:
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 引擎生命周期
- 创建:打开页面时即时创建。
- 销毁:关闭页面后,如果业务确实释放 Engine 并调用正确的销毁路径,该 Engine 的 root isolate、Widget/Element/RenderObject 树和对应资源会进入清理。但异步任务、插件、原生单例、线程、纹理和 EngineGroup/VM 共享资源可能继续存在,不能只凭 ViewController
dealloc就断言“完全销毁”。
3.2 常驻内存(Group Overhead)
- 创建过 Flutter Engine 后,Dart VM、AOT snapshot 映射、字体/渲染缓存等进程级或共享资源可能保留,以服务后续 Engine。具体由谁持有、何时释放是 Flutter 版本和平台实现细节。
- 共享基础成本通常不会按“每开一次页面”线性累积,但插件泄漏、未注销纹理、未销毁 Engine 或缓存增长仍可能让内存阶梯式上升,需要用多轮开关页面实验验证。
总结
我们的管理机制在启动速度、内存占用和开发灵活性之间取得了很好的平衡:
- 无预热:不拖慢 App 启动速度。
- 多引擎 + Group:解决状态污染问题,同时保持低内存开销。
- 瘦身模式:减小安装包体积,支持动态更新。