Skip to content
Go back

移动端 H5 是怎么和客户端说上话的

移动端 H5 页面跑在 WebView 里,它天然只能访问浏览器提供的能力。但很多业务页面并不满足于普通 Web 能力:需要打开客户端登录、获取设备信息、调起分享面板、打开相册、跳转原生页面、读取定位、上报客户端埋点。

这些能力都在客户端里,H5 不能直接调用。JS Bridge 要解决的就是这个问题:在 JS 和 Native 之间建立一条通信通道。

它看起来像是前端调用一个普通函数:

bridge.call("share.openPanel", {  title: "新车上市",  url: location.href});

但实际过程是:

H5 发起调用  -> Bridge 序列化调用信息  -> WebView 把消息传给客户端  -> 客户端解析协议并执行原生能力  -> 客户端把结果回传给 H5  -> Bridge 找到对应 callback 并 resolve

JS Bridge 的难点不在“能不能发消息”,而在于这条链路是否稳定、可追踪、可兼容、可降级。

WebView 和客户端之间有哪些通信方式

不同平台、不同 WebView 容器支持的通信方式不完全一样,但常见方案可以分成几类。

第一类是 URL Scheme 拦截。

H5 创建一个特殊协议的 URL:

location.href = "myapp://bridge?method=share.openPanel&params=...";

客户端在 WebView 的 URL 加载回调中拦截这个 URL,发现协议是 myapp://bridge,就不真的跳转,而是解析里面的方法和参数。

这种方案兼容性较好,早期很多 Bridge 都这么做。但缺点也明显:URL 长度有限,参数需要编码,连续调用时容易丢消息,也不适合传大数据。

第二类是注入 Native 对象。

Android WebView 可以通过 addJavascriptInterface 暴露对象:

window.NativeBridge.invoke(JSON.stringify(message));

客户端实现 NativeBridge.invoke,收到 JS 传过来的字符串后解析执行。

这种方式调用直接,但要考虑安全问题。暴露给 JS 的方法必须非常克制,不能把过多原生能力直接挂到全局对象上。

第三类是 WebKit MessageHandler。

iOS WKWebView 可以通过 window.webkit.messageHandlers 发消息:

window.webkit.messageHandlers.NativeBridge.postMessage(message);

这是比较现代的方式,语义清晰,也比 URL Scheme 更适合结构化消息。

第四类是客户端执行 JS 回调。

无论 H5 通过哪种方式把消息发给客户端,客户端最终都需要把结果通知给 H5。常见方式是客户端执行一段 JS:

window.__bridge__.handleNativeResponse({  callbackId: "cb_1001",  code: 0,  data: {}});

这一步是 Bridge 能支持异步调用的关键。

一次完整调用应该有 requestId

Bridge 调用不能只传方法名和参数,还必须有唯一 ID。

原因很简单:客户端能力大多是异步的。比如登录、分享、定位、选择图片,都不可能立即同步返回。H5 发出多个请求以后,客户端回来的结果必须能对应到具体请求。

一个标准消息可以这样设计:

{  "id": "bridge_10001",  "method": "share.openPanel",  "params": {    "title": "新车上市",    "url": "https://example.com"  },  "timestamp": 1536900000000}

H5 侧保存 callback:

const callbacks = new Map();function call(method, params) {  const id = createBridgeId();  const promise = new Promise((resolve, reject) => {    callbacks.set(id, { resolve, reject });  });  sendToNative({    id,    method,    params,    timestamp: Date.now()  });  return promise;}

客户端执行完成后回传:

{  "id": "bridge_10001",  "code": 0,  "message": "ok",  "data": {    "shared": true  }}

H5 根据 id 找到对应 Promise:

window.__bridge__.handleResponse = function(response) {  const callback = callbacks.get(response.id);  if (!callback) return;  callbacks.delete(response.id);  if (response.code === 0) {    callback.resolve(response.data);  } else {    callback.reject(response);  }};

没有 requestId 的 Bridge 只能处理简单同步指令,一旦遇到并发调用、异步回调、超时和失败重试,就会很难维护。

Bridge SDK 要屏蔽平台差异

业务页面不应该关心当前运行在 Android 还是 iOS,也不应该知道底层是 URL Scheme、MessageHandler 还是注入对象。

前端应该只面向统一 SDK:

await bridge.call("user.getLoginInfo");await bridge.call("navigation.openPage", { url: "app://car/detail?id=123" });await bridge.call("share.openPanel", { title, url });

Bridge SDK 内部再判断平台:

function sendToNative(message) {  if (isIOS() && window.webkit?.messageHandlers?.NativeBridge) {    window.webkit.messageHandlers.NativeBridge.postMessage(message);    return;  }  if (isAndroid() && window.NativeBridge?.invoke) {    window.NativeBridge.invoke(JSON.stringify(message));    return;  }  fallbackByScheme(message);}

这样业务代码不会被平台差异污染。后续客户端切换 WebView 实现,或者新增一种通信方式,也只需要升级 Bridge SDK。

客户端主动通知也要进入 Bridge 体系

Bridge 不只是 H5 调 Native,也包括 Native 主动通知 H5。

例如:

客户端登录状态变化原生页面返回后通知 H5 刷新网络状态变化App 从后台回到前台客户端主题切换

这些更适合做成事件机制:

bridge.on("app.resume", () => {  refreshPageData();});bridge.on("user.loginChange", user => {  updateUserInfo(user);});

客户端触发事件时执行:

window.__bridge__.emit("user.loginChange", {  isLogin: true,  userId: "123"});

Bridge SDK 统一分发事件。这样业务页面不需要暴露一堆全局函数给客户端调用,也能避免函数名冲突。

超时和降级不能省

Bridge 调用一定要考虑失败。

失败来源很多:

所以 SDK 必须给每次调用设置超时:

function call(method, params, options = {}) {  const timeout = options.timeout || 5000;  const id = createBridgeId();  return new Promise((resolve, reject) => {    const timer = setTimeout(() => {      callbacks.delete(id);      reject({        code: "BRIDGE_TIMEOUT",        message: `${method} timeout`      });    }, timeout);    callbacks.set(id, {      resolve: data => {        clearTimeout(timer);        resolve(data);      },      reject: error => {        clearTimeout(timer);        reject(error);      }    });    sendToNative({ id, method, params });  });}

同时,业务层要有降级方案。比如客户端分享不可用时,可以展示 Web 分享提示;客户端登录不可用时,可以跳转 Web 登录;客户端定位失败时,可以让用户手动选择城市。

Bridge 不能假设自己永远可用。一个成熟的 SDK 一定会把不可用当作常态处理。

安全边界也要设计清楚

JS Bridge 打通了 H5 和客户端能力,如果没有边界,很容易变成安全风险。

至少要考虑:

客户端不能因为 H5 传了一个 method 就直接执行。Bridge 协议层需要白名单:

trusted domains  -> allowed methods  -> parameter validation  -> permission check

前端也不能把 Bridge 当成万能接口。能用普通 Web 能力解决的问题,就不要强依赖客户端能力;必须调用客户端时,也要考虑用户权限和失败路径。

小结

JS Bridge 的本质是 WebView 与客户端之间的一套通信协议。它不是简单挂一个全局函数,也不是随便发一个 URL Scheme。

一个可靠的 Bridge 至少要具备:

移动端 H5 和客户端交互越多,Bridge 的工程价值越明显。它把零散的端能力调用收束成统一协议,让业务页面既能使用客户端能力,又不被平台差异和版本差异拖垮。


Share this post on: