Appearance
双 WebView 引擎运输层抽象
回到总览:混合开发与桥接相关模块:WebView H5 JSBridge 交互与安全防护
一句话定义
用一个轻量 shim(适配层) 把底层 WebView 引擎的实现差异屏蔽掉——能力全的引擎与回退引擎注入时机、API 不同,但 H5 只认一套统一的调用协议(postMessage),不感知底层是哪种引擎;引擎差异收敛到一个 shim + 一个标志位,注入时机差异由就绪等待兜底。
代码索引
对应 Lab:dual-engine-transport-simulation · DualEngineTransportSimulation.java
为什么需要
- 为什么不能直接让 H5 调原生
callHandler?- 一句话答:多引擎并存时(主键引擎能力全、回退引擎兼容旧系统),两套注入时机与调用 API 不同;H5 直接调就要为每种引擎各写一套,宿主一换引擎就得改前端。
- 为什么注入时机会不同、必须用 shim 兜底?
- 一句话答:能力全的引擎可在
documentStart立即注入脚本;回退引擎常常要等onPageStarted才尽力注入,H5 早期调用会丢失,必须waitForBridge等待就绪再发。
- 一句话答:能力全的引擎可在
- 为什么「H5 不感知引擎」是好事?
- 一句话答:引擎是宿主实现细节,应与业务解耦;H5 只依赖稳定协议,宿主换引擎不波及前端,符合「宿主能力层统一收口」的分层。
底层机制
1. 运输层接口 + 双引擎实现
把两种引擎抽象成同一个 BridgeTransport 接口;差异(名字、就绪时机、调用前缀)被封装在实现内部。
对应 Lab:dual-engine-transport-simulation · DualEngineTransportSimulation.java
java
// 运输层接口:屏蔽底层 WebView 实现差异
interface BridgeTransport {
String name();
boolean ready(); // 注入是否就绪(注入时机不同)
String call(String handler, String payload);
}
// 实现 A:能力全的引擎,documentStart 立即注入
class InAppTransport implements BridgeTransport {
public String name() { return "inappwebview"; }
public boolean ready() { return true; }
public String call(String h, String p) { return "[inapp]" + h + "(" + p + ")"; }
}
// 实现 B:回退引擎,onPageStarted 才尽力注入
class FallbackTransport implements BridgeTransport {
public String name() { return "webview_flutter"; }
public boolean ready() { return false; }
public String call(String h, String p) { return "[fallback]" + h + "(" + p + ")"; }
}2. shim 统一入口 + 标志位
H5 永远只调 shim.postMessage;当前用的是哪种引擎,由 useInApp 标志(等价于 window.__USE_INAPP_TRANSPORT__)与一个 transport 实例决定,H5 完全不碰。
java
// H5 统一调用的 shim:内部转调当前运输层
static class BridgeShim {
BridgeTransport transport;
boolean useInApp; // 等价于 window.__USE_INAPP_TRANSPORT__
String postMessage(String handler, String payload) {
return transport.call(handler, payload); // H5 不感知底层引擎
}
}Android / Flutter / Web / Backend 对照
| 维度 | Android WebView | Flutter WebView | H5 / Web | 后端类比 |
|---|---|---|---|---|
| 引擎并存 | 单一系统 WebView | webview_flutter / flutter_inappwebview 可并存 | shim 屏蔽差异 | 多 SDK 适配层(如多支付渠道) |
| 注入时机 | onPageStarted / addJavascriptInterface | documentStart UserScript vs onPageStarted | 只调 shim | — |
| 统一协议 | 由原生侧 shim 收敛 | 由原生侧 shim 收敛 | 只认 postMessage | 门面模式(Facade) |
常见场景
1. 主键引擎注入失败,自动回退
宿主检测到主键引擎不可用,把 transport 切到回退实现、useInApp=false;H5 代码一行不改,只是调用经不同 transport 落地。
对应 Lab:dual-engine-transport-simulation · 场景 2(回退引擎路径)
2. H5 早期调用 waitForBridge
首屏早期逻辑若依赖桥,必须等 waitForBridge 就绪再发(回退引擎注入晚);就绪前调用会静默丢失。
常见误配、事故后果与排障
- 误配:H5 直接调原生
callHandler,绕过 shim。- 后果:换引擎时必须重写 H5;或多引擎下行为不一致。
- 排障:统一收敛到 shim,禁止业务模块直连引擎 API。
- 误配:忽略注入时机差异,首屏早期桥调用未
waitForBridge。- 后果:回退引擎下首屏桥调用丢失,功能偶发失效。
- 排障:所有早期桥调用包一层就绪等待。
与相近概念对比
- 运输层抽象 vs 简单 if 分支:if 分支把引擎差异散落到每个调用点;shim 把差异收敛到一处,新增引擎只加一个实现。
- 运输层抽象 vs 适配器模式:本质就是适配器/策略模式在 WebView 引擎上的应用,强调「H5 只认协议、引擎可替换」。
对应实验
| Lab | 说明 | 源码 |
|---|---|---|
| dual-engine-transport-simulation | 双引擎 shim 抽象仿真:H5 统一 postMessage,引擎差异被屏蔽 | DualEngineTransportSimulation.java |
复习检查题
为什么要把 WebView 引擎差异收敛到一个 shim,而不是在 H5 里为每种引擎写分支?
答:引擎是宿主实现细节,与业务无关;收敛到 shim 后,H5 只依赖稳定协议,宿主换引擎(或加回退引擎)只需新增一个
transport实现,前端零改动。若把差异散落到 H5 每个调用点,换引擎就要全量改前端,且容易各点行为不一致。为什么即使有了 shim,H5 仍需
waitForBridge兜底?答:回退引擎的注入发生在
onPageStarted之后,比主键引擎的documentStart晚;首屏早期桥调用若在脚本注入完成前发出会静默丢失。shim 解决「调哪个引擎」,但不解决「引擎还没就绪」,所以必须由就绪等待兜底。
速记
- 引擎差异收敛到一个 shim + 一个
useInApp标志。 - H5 只认
postMessage,不感知底层引擎。 - 注入时机差异(documentStart vs onPageStarted)由
waitForBridge兜底。 - 换引擎只加一个
transport实现,前端零改动。