Skip to content

桥接边界与错误协议 ​

回到总览:混合开发与桥接相关模块:WebView H5 JSBridge 交互与安全防护

一句话定义 ​

桥接工程化最容易翻车的不是「能不能通」,而是三类边界处理:① 大整数精度(snowflake id 超过 JS number 精度,必须字符串化);② 返回栈协商(goBack 先问 H5,H5 有历史就消费一条、无历史/超时才交还原生关页);③ 错误信封(桥错误用 {__isBridgeError:true} 包裹,让 H5 区分「原生报错」与「正常返回」)。

代码索引 ​

对应 Lab:bridge-boundary-error-protocol-simulation · BridgeBoundaryErrorProtocolSimulation.java

为什么需要 ​

  • 为什么更大的 ID(如雪花 id)不能直接走 JSON 传给 H5?
    • 一句话答:JS 的 number 是双精度浮点,只有 53 位尾数;超过 2⁵³ 的整数(典型雪花 id)被解析后会丢精度,前端拿到错误的 id。必须双重编码成字符串传。
  • 为什么原生 goBack 不能直接关页,要先问 H5?
    • 一句话答:H5 内部还有自己的路由栈(如 Vue Router);原生一刀切关页会让用户在 H5 内部还有历史时误退出 App。必须先让 H5 协商:有历史就消费一条,没历史才关。
  • 为什么桥返回要用独立的错误信封,而不是塞个 error 字段?
    • 一句话答:塞字段不够彻底,容易被业务逻辑当成普通数据漏判;用独立错误信封(__isBridgeError),H5 渲染层一眼识别,绝不会把异常当业务数据渲染。

底层机制 ​

1. 大整数精度:双重编码成字符串 ​

跨 JS 边界的数字默认走 number。大整数必须字符串化,让 JS 当字符串解析而非当 number。

对应 Lab:bridge-boundary-error-protocol-simulation · 场景 1 · BridgeBoundaryErrorProtocolSimulation.java

java
// 大整数双重 encode:让 JS 当 string 解析,不当 number 丢精度
long snowflakeId = 1872039485720394857L; // > 2^53
String safe = "\"" + snowflakeId + "\"";  // 等价于 JSON.encode(JSON.encode(id))
// H5 侧用 parseJsonPreservingId 解析,保留完整精度

2. 返回栈协商:goBack 先问 H5 ​

goBack 触发时,原生先问 H5 __tryGoBack;H5 有路由历史就消费一条(页面不关),无历史或超时未答才交还原生关页。原生侧还要兼容不同 WebView 实现对返回值的类型差异(bool / string / int)。

3. 错误信封:区分原生异常与正常返回 ​

桥错误双向用 {__isBridgeError:true} 包裹;H5 判断是否含该标记来分流「异常」与「正常数据」。

java
// 错误信封:区分原生异常与正常返回
static String normalReturn(String data) {
    return "{data:" + data + "}";
}
static String errorReturn(String code) {
    return "{__isBridgeError:true, code:\"" + code + "\"}";
}

Android / Flutter / Web / Backend 对照 ​

维度Android / 原生Flutter / DartH5 / Web后端类比
大整数long 原生精度足够int 精度足够number 仅 53 位尾数同 JS,需字符串化
返回栈onBackPressed 协商PopScope / 路由栈协商history.back() / Router前端路由守卫
错误协议异常对象Result / 异常错误信封gRPC status / HTTP 错误码

常见场景 ​

1. 列表项 id 用雪花 id,详情页拿到错误 id ​

服务端返回雪花 id,若直接 JSON 数字传 H5,详情请求会带错 id。必须字符串化。

对应 Lab:bridge-boundary-error-protocol-simulation · 场景 1

2. H5 内多层跳转后点返回 ​

goBack 先问 H5,H5 逐层退出路由栈;栈空才真正关页。

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

  • 误配:ID 直接 JSON 数字传。
    • 后果:雪花 id 末尾几位错乱,详情页张冠李戴。
    • 排障:跨 JS 边界的大整数一律字符串化。
  • 误配:原生 goBack 直接关页。
    • 后果:H5 内部还有历史时误退 App,用户抱怨「退不回去」。
    • 排障:先 __tryGoBack 协商,带超时兜底。
  • 误配:桥返回里塞 error 字段区分。
    • 后果:业务逻辑漏判,把异常当数据渲染。
    • 排障:用独立错误信封,渲染层强制分流。

与相近概念对比 ​

  • 错误信封 vs HTTP 状态码:HTTP 状态码在传输层就分流;桥接没有现成状态码通道,所以用信封在 payload 内显式标记。
  • 返回栈协商 vs 原生返回拦截:原生拦截只知「要返回了」,不知 H5 内部栈深度;协商让 H5 自报是否有历史,决策更准。

对应实验 ​

Lab说明源码
bridge-boundary-error-protocol-simulation大整数精度 / 返回栈协商 / 错误信封三者仿真BridgeBoundaryErrorProtocolSimulation.java

复习检查题 ​

  1. 为什么雪花 id(snowflake id)跨桥传给 H5 时必须字符串化,而不能直接当 JSON 数字传?

    答:JS 的 number 是双精度浮点,尾数只有 53 位;雪花 id 通常超过 2⁵³,被 JSON 解析成 number 时会丢失低位精度,前端拿到的 id 与后端不一致,导致详情页/关联查询张冠李戴。双重编码成字符串可让 JS 当字符串原样保留。

  2. 为什么 goBack 要先问 H5,而不是直接由原生关页?

    答:H5 内部有自己的路由栈(如 Vue Router),用户在 H5 里可能还有多层历史;原生一刀切关页会把「H5 内部返回」误判成「退出 App」。先让 H5 协商——有历史就消费一条、没历史或超时未答才交还原生关页——返回语义才正确。

速记 ​

  • 大 ID 用字符串传,避免 JS number 精度丢失(> 2⁵³)。
  • goBack 先协商:H5 有历史消费一条,无历史/超时才关页。
  • 错误信封 __isBridgeError:让 H5 一眼区分「原生异常」与「正常数据」。
  • 跨 WebView 实现对返回值类型(bool/string/int)都要兼容。

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