JS Bridge 的版本兼容问题,来自 H5 和客户端完全不同的发布节奏。
H5 页面可以随时发布,今天改完今天上线。客户端 App 需要发版、审核、用户升级,线上会长期存在多个版本。于是很容易出现这种情况:
H5 已经调用了新 Bridge 方法但一部分用户的客户端还不支持
如果 Bridge SDK 没有处理好兼容,业务页面会出现各种问题:按钮点了没反应、分享失败、登录调不起来、图片选择异常、页面跳转不到原生页。
所以一个成熟的 JS Bridge 库,不只是把消息送到客户端,还要处理版本差异。
不要只用 App 版本判断能力
最容易想到的做法是判断 App 版本:
if (appVersion >= "8.5.0") { bridge.call("image.choose");}
这种方式能用,但不够稳定。
原因有几个:
- Android 和 iOS 同一个版本支持能力可能不同。
- 某个能力可能灰度发布,不是所有 8.5.0 用户都有。
- 不同 WebView 容器可能支持不同 Bridge 集合。
- 某个客户端版本可能临时回滚过能力。
- App 版本比较本身也容易写错。
更可靠的是能力探测。
H5 不问“你是不是 8.5.0”,而是问“你支不支持这个方法”:
const supported = await bridge.canIUse("image.choose");if (supported) { await bridge.call("image.choose");} else { showUploadFallback();}
客户端提供能力表:
{ "bridgeVersion": "2.4.0", "platform": "ios", "methods": { "share.openPanel": { "since": "1.0.0" }, "image.choose": { "since": "2.1.0" }, "navigation.openPage": { "since": "1.0.0" } }}
Bridge SDK 初始化后缓存能力表,业务调用前通过 canIUse 判断。
SDK 要有初始化状态
Bridge SDK 不是页面一加载就一定可用。客户端注入对象可能比业务 JS 执行更晚,尤其是一些 Android WebView 或异步注入场景。
因此 SDK 要有 ready 机制:
await bridge.ready();await bridge.call("user.getLoginInfo");
ready 做几件事:
等待客户端注入完成获取 Bridge 能力表记录平台、App 版本、Bridge 协议版本初始化 callback 和事件分发
如果超时仍然没有 ready,SDK 要明确进入不可用状态:
try { await bridge.ready({ timeout: 3000 });} catch (error) { renderWebFallback();}
业务代码不应该自己判断 window.NativeBridge 是否存在。这个判断应该被 SDK 收敛。
API 设计要默认支持降级
Bridge SDK 对业务暴露的 API,最好天然包含降级意识。
例如分享能力:
await bridge.share({ title, url, fallback() { showCopyLinkPanel(); }});
内部逻辑可以是:
检查 Bridge 是否 ready检查 share.openPanel 是否支持调用客户端分享如果失败或不支持,执行 fallback
这样业务接入时不会每次都手写一堆兼容判断。
也可以提供通用调用:
bridge.call("share.openPanel", params, { fallback: () => showCopyLinkPanel(), timeout: 5000});
SDK 的目标不是让业务“能调到客户端”,而是让业务在不同客户端版本下都有稳定行为。
协议字段只能向前兼容
Bridge 兼容性最容易出问题的是协议字段变化。
比如一个方法最初这样设计:
{ "method": "navigation.openPage", "params": { "url": "app://car/detail?id=123" }}
后来希望支持无动画跳转,不应该改变 url 的含义,而应该新增可选字段:
{ "method": "navigation.openPage", "params": { "url": "app://car/detail?id=123", "animated": false }}
旧客户端不认识 animated 时可以忽略,新客户端可以使用它。这就是向前兼容。
不推荐的改法是:
{ "method": "navigation.openPage", "params": { "url": { "value": "app://car/detail?id=123", "animated": false } }}
这会改变 url 的类型,旧客户端解析时很可能直接失败。
协议演进要遵守:
能新增可选字段,就不要改旧字段能新增 method,就不要改变旧 method 语义旧字段可以废弃,但不要马上删除客户端解析未知字段时应忽略H5 调用新字段时必须准备降级
方法废弃需要有过渡期
Bridge 方法一旦被业务页面使用,就不能随意删除。因为线上可能存在旧 H5、旧活动页、缓存页,也可能有用户长期停留在旧客户端版本。
废弃方法可以分阶段:
阶段一:标记 deprecated,但继续可用阶段二:SDK 开发环境 warning阶段三:业务迁移到新方法阶段四:客户端保留兼容转发阶段五:确认线上无调用后再下线
例如旧方法:
bridge.call("share", params);
新方法:
bridge.call("share.openPanel", params);
客户端可以在一段时间内兼容:
share -> share.openPanel
SDK 也可以在开发环境提醒:
[Bridge] method "share" is deprecated, use "share.openPanel" instead.
这种兼容看起来麻烦,但比线上页面突然不可用要可靠得多。
不同端能力要保持同名同义
Bridge 方法如果在 Android 和 iOS 上同名,就必须保证语义一致。
例如:
image.choose
如果 iOS 返回:
{ "localId": "xxx", "width": 100, "height": 100}
Android 返回:
{ "path": "file://xxx"}
那业务页面就被迫写平台判断,Bridge SDK 的意义会被削弱。
更好的方式是 SDK 或客户端统一返回结构:
{ "images": [ { "id": "xxx", "uri": "file://xxx", "width": 100, "height": 100 } ]}
如果平台确实有差异,也应该放在明确字段里:
{ "platformExtra": {}}
但主路径返回结构要一致。
SDK 要内置错误码映射
客户端返回的错误码如果没有规范,业务会很难处理。
SDK 应该把底层错误统一成稳定错误类型:
try { await bridge.call("image.choose");} catch (error) { if (error.code === "METHOD_NOT_SUPPORTED") { showFallback(); }}
底层可能来自不同端:
iOS: -1001Android: 404Scheme: timeout
SDK 对外统一成:
METHOD_NOT_SUPPORTEDINVALID_PARAMSPERMISSION_DENIEDUSER_CANCELBRIDGE_TIMEOUTNATIVE_ERROR
这样业务代码不需要知道不同端内部错误码。
灰度能力要能被识别
客户端能力有时不是随着版本全量发布,而是灰度开放。
例如 8.8.0 版本里只有 20% 用户支持新的分享面板。如果 H5 只判断版本,就会误以为所有 8.8.0 都支持。
能力表可以包含灰度结果:
{ "methods": { "share.openNewPanel": { "supported": true, "since": "2.5.0", "gray": true } }}
H5 只根据 supported 判断,不直接根据版本猜。
这对客户端渐进发布非常重要。Bridge SDK 要承认线上环境不是整齐的,能力支持情况可能因用户、端、版本、实验分组而不同。
调试和监控是兼容性的保障
版本兼容问题很难靠开发阶段完全覆盖。线上一定会遇到各种组合:
新 H5 + 旧 App旧 H5 + 新 App灰度 App + 新 Bridge SDK低版本 WebView + 新页面缓存页面 + 已废弃方法
所以 SDK 要上报关键指标:
- Bridge ready 成功率。
- method not supported 次数。
- 调用超时次数。
- 各方法成功率。
- 平台和 App 版本分布。
- 客户端返回错误码分布。
- fallback 触发次数。
这些数据能帮助判断一个新 Bridge 能力是否可以扩大使用,也能发现某个客户端版本是否存在实现问题。
开发阶段则需要调试面板:
Bridge Version: 2.4.0App Version: 8.9.0Platform: AndroidMethods: 42[success] user.getLoginInfo 42ms[failed] image.choose METHOD_NOT_SUPPORTED[timeout] share.openPanel 5000ms
没有调试和监控,Bridge 兼容性问题只能靠用户反馈,定位成本会很高。
小结
JS Bridge 库的版本兼容,本质上是在调和两个发布节奏:H5 快,客户端慢。
一个可靠的 Bridge SDK 应该做到:
- 用能力探测代替单纯 App 版本判断。
- 提供 ready 机制,收敛注入时机差异。
- API 默认支持 fallback 和 timeout。
- 协议字段保持向前兼容。
- 方法废弃要有迁移过渡期。
- Android 和 iOS 返回结构保持同名同义。
- SDK 统一错误码。
- 能识别灰度能力。
- 提供调试面板和线上监控。
JS Bridge 的设计不能只面向当前版本。它必须默认面对混乱的线上版本分布。只有把兼容性设计进 SDK 和协议里,移动端 H5 才能稳定使用客户端能力。