Native 和 Flutter 通信你怎么设计?如何避免通道失控和协议膨胀?

很多团队在 Flutter 混合开发初期都很顺:MethodChannel 一接就通,需求推进很快。
但一到中后期,问题会集中爆发:

  • Channel 越开越多,没人说得清入口边界
  • Method 名称和参数随意增长,协议越来越难维护
  • 页面和平台代码耦合,改一处牵一片

这篇文章只回答两个问题:

  1. Native 和 Flutter 通信应该怎么设计?
  2. 如何避免通道失控和协议膨胀?

1. 先定目标:通信不是“打通就行”,而是“长期可治理”

通信层本质上是一个进程内 API 系统。
既然是 API,就要有三件事:

  • 明确边界:哪些能力可以跨端暴露
  • 稳定协议:请求/响应结构可演进
  • 工程治理:可观测、可约束、可下线

一句话:把 Channel 当成基础设施,而不是业务临时脚手架。

2. 推荐架构:Transport / Router / Adapter 三层收口

实践里最稳的方式,不是“每个功能一个 Channel”,而是“三层分责”。

2.1 Transport 层:少量固定通道

  • 只保留少数固定 Channel
  • 按职责拆分,而不是按页面拆分
  • 例如:生命周期通道、业务能力通道、事件通道

目标是控制“通道数量”,避免入口泛滥。

2.2 Router 层:统一路由出口

页面跳转优先走统一 Router(如 URL/Scheme 或路由表),不要把“打开页面”设计成大量零散 method:

  • 不要出现 openPageA/openPageB/openPageC
  • 使用统一的 openPage(pageUrl, params)

目标是控制“调用入口”,避免各模块自行发明接口。

2.3 Adapter 层:业务只依赖 Adapter,不直接碰 Channel

业务代码只调用 Adapter API,例如:

  • openPage(...)
  • openWeb(...)
  • getUserInfo()
  • uploadImage(...)

而不允许业务层直接 MethodChannel.invokeMethod(...)

目标是把平台差异和协议细节封装在 Adapter 内,避免污染业务层。

3. 如何防“通道失控”:靠结构约束,不只靠口头约定

很多团队说“规范里写了不允许乱加”,但规范本身不是约束。
真正有效的是“想乱加也很难加进去”。

3.1 依赖约束

  • 业务模块禁止直接 import Channel 实现文件
  • 仅允许 import Adapter/Facade
  • Channel 代码放在基础设施目录,收窄可见性

3.2 注册约束

  • 插件注册集中在单一入口(模块初始化/App 启动)
  • 对重复注册做 fail-fast(直接断言或报错)
  • 禁止页面级或业务模块私自注册插件

3.3 审查约束

在 CI 增加静态检查,阻断“绕过主干路径”的提交。最实用两类规则:

  1. Dart 侧禁用规则

    • 扫描业务目录中 MethodChannel(invokeMethod( 直接调用
    • 命中即 CI fail
  2. Native 侧禁用规则

    • 扫描非基础设施目录中的 FlutterMethodChannel 新增
    • 扫描未登记的插件注册入口

这样即使有人不遵守,也过不了流水线。

4. 如何防“协议膨胀”:统一信封 + 版本策略 + 类型化

协议膨胀的核心不是字段多,而是语义失控。
治理重点是“统一”和“可演进”。

4.1 统一请求/响应信封

建议统一 envelope,至少包含:

{
  "traceId": "uuid",
  "module": "user",
  "action": "getProfile",
  "version": 1,
  "data": {}
}

返回统一:

{
  "traceId": "uuid",
  "code": 0,
  "message": "ok",
  "data": {}
}

好处是日志、监控、排障模型一致。

4.2 字段演进规则

  • 只增不改:新增可选字段,不复用旧字段语义
  • 显式废弃:deprecated 后走下线周期
  • 大变更升版本:version 或新 action

4.3 类型化协议

  • 优先使用 Pigeon/IDL 或至少统一 DTO
  • 减少 Map<String, dynamic> + 字符串 key 漫游

类型化是控制维护成本最有效的长期投资。

5. 面试可直接讲的设计答案

如果面试官问“你怎么设计 Native 和 Flutter 通信”,可以用这段:

我会先做收口,再做治理。
第一层是通道收口:只保留少数固定 Channel,按职责划分,不按页面划分。
第二层是调用收口:业务只走 Adapter 和 Router,禁止直接调用 MethodChannel。
第三层是协议收口:统一 request/response envelope,做版本和错误码治理。
第四层是工程化硬约束:用 CI 扫描直接 Channel 调用和非法注册,确保规则可执行。
这样可以同时解决“通道失控”和“协议膨胀”,并支持后续持续演进。

6. 一份可落地的治理清单

上线前建议至少完成以下项目:

  1. 统一 Bridge SDK(Flutter / iOS / Android)
  2. 统一 Adapter/Facade,业务层禁直连 Channel
  3. 统一协议 envelope(含 traceId
  4. 统一错误码分层(通用码 + 领域码)
  5. CI 静态规则拦截直连调用和非法注册
  6. 监控看板(成功率、P95、超时率、Top 错误码)
  7. 接口 owner 制度(文档、SLA、下线策略)

结语

Native-Flutter 通信的关键,不是“能不能调通”,而是“半年后还能不能稳”。
把它当成一个受治理的 API 系统来设计,才是避免失控和膨胀的根本解法。