WebView 中 WebSocket 与 H5 状态同步方案

图片来自
https://www.pexels.com/zh-cn/photo/macbook-209701/
一、场景
APP 内嵌一个 WebView,WebView 中运行 H5 页面。
当前系统存在以下条件:
- APP 已经建立并维护 WebSocket 长连接。
- H5 页面本身有大量 HTTP API,与后端进行业务数据交互。
- H5 需要实时感知部分状态变化,状态可能在 1 分钟内变化 3~5 次。
- H5 不允许单独建立 WebSocket,否则会与 APP 的 WebSocket 连接产生冲突。
- H5 收到状态变化后,需要及时更新页面数据。
因此需要解决的问题是:
如何利用 APP 已有的 WebSocket,将实时状态变化传递给 H5,同时继续保持 H5 原有的 API 数据获取模式。
二、方案一:H5 单独建立 WebSocket
结构:
后端
├── WebSocket → APP
└── WebSocket → H5不推荐。
H5 如果再次建立 WebSocket,会产生多个实时连接,同时与现有 APP WebSocket 的连接管理产生冲突。
如果后端同一用户只允许一个 WebSocket 连接,还可能导致 APP 的 WebSocket 被 H5 挤掉,从而影响 APP 本身的消息推送。
因此在当前限制下,H5 不应该直接建立 WebSocket。
三、方案二:APP WebSocket → JS Bridge → H5
推荐采用 APP 作为唯一的 WebSocket 客户端。
数据流:
后端 WebSocket
↓
APP WebSocket
↓
Native
↓
JS Bridge
↓
H5APP 收到 WebSocket 消息后,通过 WebView 的 JavaScript Bridge 通知 H5。
例如 WebSocket 收到:
{
"type": "status_changed",
"id": 123
}Native 将事件发送给 H5:
window.NativeBridge.emit("status_changed", {
id: 123
});H5 收到事件后处理对应业务。
这种方式可以避免 H5 建立第二个 WebSocket。
四、不要让 WebSocket 消息成为 H5 的数据源
由于 H5 本身已经存在大量 HTTP API,因此更合理的设计是:
WebSocket 负责发送“变化通知”,HTTP API 负责获取“最新状态”。
例如:
后端状态发生变化
↓
WebSocket
↓
APP
↓
JS Bridge
↓
H5 收到 status_changed
↓
调用 HTTP API
↓
获取最新状态
↓
更新 UI例如 H5 收到:
{
"type": "task_changed",
"taskId": 123
}然后调用:
GET /api/task/123获取最新数据。
这样可以继续复用 H5 原有的 API、数据模型和业务逻辑,而不需要在 Android Native 中重新实现一套业务数据处理逻辑。
五、推荐采用事件机制
不建议在 Native 中直接调用大量具体的 JavaScript 方法:
refreshUser()
refreshTask()
refreshOrder()
refreshRoom()
refreshStatus()随着业务增加,这种方式会导致 Native 与 H5 强耦合。
可以设计统一的事件通道:
Native WebSocket
↓
Native EventBus
↓
JS Bridge
↓
H5 EventBus
↓
业务模块
↓
HTTP APIH5:
NativeBridge.on("task_changed", (data) => {
refreshTask(data.taskId);
});以后新增实时事件时,只需要增加事件类型:
user_changed
task_changed
order_changed
room_changed
status_changed
message_received六、状态变化频繁时的处理
如果一分钟内产生 3~5 次状态变化,通常不需要每次都立即发起 HTTP 请求。
可以在 H5 侧进行短时间防抖:
let timer = null;
NativeBridge.on("status_changed", () => {
clearTimeout(timer);
timer = setTimeout(() => {
refreshStatus();
}, 100);
});如果短时间内连续收到多个通知:
A
B
C可以合并成一次 API 请求。
七、需要考虑请求乱序
实时状态同步还需要考虑 HTTP 请求返回顺序。
例如:
状态 A → 请求 API A
状态 B → 请求 API B
API B 先返回
API A 后返回如果直接使用返回结果更新 UI,可能出现旧状态覆盖新状态的问题。
可以通过服务端版本号、时间戳或序列号解决:
{
"status": "online",
"version": 123
}H5 只接受不低于当前版本的数据。
if (data.version >= currentVersion) {
updateUI(data);
}八、WebView 生命周期
还需要考虑 WebView 当前是否已经准备完成。
例如:
WebSocket 收到消息
↓
WebView 尚未创建此时不能直接执行 JavaScript。
可以由 Native 缓存最新事件,在 H5 页面初始化完成后再发送。
典型流程:
WebSocket
↓
Native 保存最新状态
↓
WebView 创建
↓
H5 Ready
↓
Bridge 建立
↓
发送最新状态事件这样可以避免 WebSocket 消息丢失。
九、最终方案
综合当前场景,推荐采用:
Backend
┌────────────┐
│ HTTP API │
│ WebSocket │
└─────┬──────┘
│
┌────────┴────────┐
│ │
WebSocket │ HTTP
│ │
▼ ▼
┌───────────┐ ┌───────────┐
│ APP │ │ H5 │
│ WebSocket │ │ API Client│
└─────┬─────┘ └─────▲─────┘
│ │
│ JS Bridge │
▼ │
┌───────────┐ │
│ H5 │─────────────┘
│ EventBus │
└───────────┘核心职责划分:
| 模块 | 职责 |
|---|---|
| APP WebSocket | 唯一实时连接 |
| JS Bridge | Native 与 H5 之间传递事件 |
| H5 EventBus | 分发实时状态变化 |
| H5 HTTP API | 获取最新业务数据 |
| H5 UI | 根据最新数据更新页面 |
最终形成:
WebSocket 负责通知,HTTP API 负责数据,JS Bridge 负责通信,H5 负责业务状态。
这种设计可以避免重复建立 WebSocket,同时最大程度复用现有 H5 API 架构,并降低 Android Native 与 H5 业务逻辑之间的耦合。