Skip to content

桥接契约版本治理:把 JSBridge 当作版本化 API 表面 ​

回到总览:混合开发与桥接相关模块:WebView H5 JSBridge 交互与安全防护 · 跨端一致性设计:多端架构分层与设计系统

一句话定义 ​

把原生 ↔ H5 的桥接通道当成一套版本化 API 表面来管理:每个通道是一个「契约(名字 + 版本号)」,破坏性变更必须升版本;由宿主在 WebView 全局注入一份「桥接元信息(bridge meta)」,H5 据此做能力探测与降级适配;再用一条 CI 校验把「契约漂移」挡在编译前。

代码索引 ​

对应 Lab:contract-consistency-simulation · ContractConsistencySimulation.java

为什么需要 ​

  • 为什么 JSBridge 不能像内部函数调用那样随意改返回字段?
    • 一句话答:H5 是独立发布周期的前端,与原生壳的发版节奏不同;原生偷偷改了某个通道的返回结构,线上 H5 不会同步更新,立刻大面积报错。通道必须显式版本化,让两端按契约协商。
  • 为什么只有「加可选字段」才算向后兼容,其它改动都不算?
    • 一句话答:旧端只取已知字段、忽略未知字段才能继续跑;一旦类型变更、删字段、把可选变必填,旧端解析即崩溃——这些才是破坏性变更,必须升版本号。
  • 为什么要在 WebView 全局注入 bridge meta,而不是直接约定「双方都用 v2」?
    • 一句话答:同一份 H5 可能同时被多个版本的原生壳加载(老包用户没升级);H5 只能在运行时探测「当前壳支持哪些通道、什么版本」,再决定走哪套适配逻辑,编译期约定无法覆盖多版本并存。

底层机制 ​

1. 契约注册表 + 元信息注入 ​

宿主侧维护一份 ChannelContract(name, version) 契约表(单一事实源);在页面加载前把元信息注入 WebView 全局,供 H5 探测能力。

对应 Lab:contract-consistency-simulation · ContractConsistencySimulation.java

dart
// 原生注册的通道契约表(单一事实源)
final contractTable = [
  ChannelContract('getDeviceInfo', 1),
  ChannelContract('openGallery', 2),    // v2 新增了 compression 参数
  ChannelContract('requestPayment', 1),
];

// 注入 WebView 全局,供 H5 探测能力(示意字段,非某仓库实现)
void injectBridgeMeta(WebViewController vc) {
  final meta = {
    'bridgeVersion': 3,
    'appVersion': '5.2.0',
    'channels': contractTable
        .map((c) => {'name': c.name, 'version': c.version})
        .toList(),
  };
  vc.runJavaScript('window.__BRIDGE_META__ = ${jsonEncode(meta)}');
}

关键点:契约表是「单一事实源」,多端都从这里生成/校验;注入的 meta 只是它的运行时投影,解决「同一 H5 跑在多版本壳」的探测问题。

2. H5 能力探测与降级适配 ​

H5 读 meta,对需要的能力查版本;低版本走 normalizers(适配层)把老结构补成新结构;缺失通道则禁用对应功能并提示升级。

javascript
// H5 侧能力探测(示意字段,非某仓库实现)
function channelVersion(name) {
  const meta = window.__BRIDGE_META__;
  const ch = (meta?.channels || []).find(c => c.name === name);
  return ch ? ch.version : 0; // 0 = 当前壳不支持
}

// openGallery v2 才有 compression 参数,老壳走无参路径
function openGalleryAdapted() {
  if (channelVersion('openGallery') >= 2) {
    return nativeCall('openGallery', { compression: 'high' });
  }
  return nativeCall('openGallery', {}); // 老壳走适配路径
}

3. CI 强校验:契约漂移挡在编译前 ​

提交时跑一条校验:① 注册通道集合必须等于契约表集合(不能注册了却不登记,或登记了却没实现);② 同名通道版本号在所有登记处必须一致。不一致直接 fail 构建。

对应 Lab:contract-consistency-simulation · 场景 6(演示「注册表 vs 契约表」CI 校验的失败判定)

bash
# 契约漂移校验思路(示意,非某仓库实现)
fail=0
# 1. 注册的通道名集合 必须等于 契约表集合
comm -3 <(list_registered | sort) <(list_contracts | sort) && fail=1
# 2. 同名通道版本必须一致
diff <(list_registered_with_version) <(list_contracts_with_version) && fail=1
[ $fail -eq 0 ] && echo "契约一致 ✅" || { echo "契约漂移 ❌"; exit 1; }

Android / Flutter / Web / Backend 对照 ​

维度Android WebViewFlutter WebViewH5 / Web类比 Backend
契约登记Java 侧维护契约表Dart 侧维护契约表读 window.__BRIDGE_META__ 探测契约即 OpenAPI Schema
元信息注入evaluateJavascriptrunJavaScript只读API 版本走 Header/URL
版本治理CI 校验脚本同左normalizers 适配网关 API 兼容性测试

桥接版本治理与 REST API 版本治理本质相同(都是版本化 API 表面),差异只在:桥接的「客户端」是 H5、版本信息不走 HTTP Header 而走注入的 meta;CI 校验代替了网关的 API 兼容性测试。

常见场景 ​

1. 给 openGallery 加 compression 参数(非破坏性) ​

新增参数设为可选:旧 H5 不传也能跑,新 H5 传了新壳才生效。无需升版本,旧端按「忽略未知参数」天然兼容。

对应 Lab:contract-consistency-simulation · 场景 2(加可选字段兼容)

2. 把 getUserProfile 返回值扁平→嵌套(破坏性,必须升版本) ​

结构变化会让旧 H5 取不到字段。必须升 version,并在 meta 中体现;旧壳用户要么走 normalizers 适配,要么提示升级。

对应 Lab:contract-consistency-simulation · 场景 3(类型变更破坏性)

常见误配、事故后果与排障 ​

  • 误配:「我改了返回结构但没升版本,因为只多了一个字段」——实际是重命名/类型改,旧端崩溃。
    • 后果:线上旧 H5 大面积解析失败。
    • 排障:先看 meta.channels 里该通道版本,确认两端是否一致;类型变更必须升版本。
  • 误配:「约定好都用 v2 就行,不用注入 meta」——老壳用户加载新 H5。
    • 后果:功能缺失或崩溃。
    • 排障:H5 必须在运行时探测 meta,不能硬编码版本。
  • 误配:注册了通道却忘了登记进契约表(或版本写错)。
    • 后果:契约漂移,靠人工 review 极易漏。
    • 排障:CI 强校验拦截(见场景 6)。

与相近概念对比 ​

  • 契约版本 vs REST API 版本:机制同源;桥接把「版本信息」放在注入的 meta 而非 HTTP Header,CI 校验替代网关兼容性测试。
  • 契约版本 vs 普通白名单:白名单解决「谁能调」(安全),契约版本解决「调的是什么结构」(兼容性),两者正交、都要做。

对应实验 ​

Lab说明源码
contract-consistency-simulation契约兼容/破坏仿真 + 注册表 vs 契约表 CI 强校验ContractConsistencySimulation.java

复习检查题 ​

  1. 为什么给桥接通道「加一个可选字段」是安全的,而「把字段从 String 改成数组」是破坏性变更?

    答:旧端只取已知字段、忽略未知字段,所以多一个可选字段它照样能跑;但类型一变,旧端按原类型强转就会 ClassCastException 崩溃。判断标准不是「字段数量」,而是「旧端能否在不改代码的情况下继续正确解析」。

  2. 既然已经约定好「都用 v2」,为什么还要在运行时注入 bridge meta 让 H5 探测?

    答:H5 和原生壳的发布节奏不同,同一份 H5 会被多个版本的原生壳加载(老包用户未必升级)。H5 无法在编译期确定自己运行在哪个壳里,只能在运行时探测当前壳支持的通道与版本,再走对应适配逻辑;约定「都用 v2」只适用于强管控的单壳场景。

速记 ​

  • 桥接通道 = 版本化 API 表面;破坏性变更必升版本。
  • meta 注入解决「同一 H5 跑在多版本壳」的探测问题。
  • 兼容性靠「旧端忽略未知字段」;破坏性是「类型 / 必填 / 删字段」。
  • CI 校验:注册表 == 契约表、同名版本一致,挡住契约漂移。

站点构建时间:2026/8/24 23:43:17