一、原生 → H5
1. runJavaScript
原生侧主动执行任意 JS 脚本,是最直接的原生调用 H5 方式。
webController.runJavaScript("functionName('params')");
webController.runJavaScript("document.title"); // 也能获取返回值(Promise)
2. javaScriptOnDocumentStart
在 HTML 文档解析之前注入 JS 脚本,适用于注入全局变量、polyfill、监控脚本等。
Web({ src: $url, controller: webController })
.javaScriptOnDocumentStart('window.NATIVE_ENV = "harmonyos";')
二、H5 → 原生
3. javaScriptProxy(对象注册)
原生注册一个对象暴露给 H5 调用,是 H5 调用原生最主流、最可靠 的方式。
// 原生侧
class JSBridge {
callNative(funcId: string, data: string): string {
// 处理 H5 调用
return "result";
}
}
let bridge = new JSBridge();
Web({ src: $url, controller: webController })
.javaScriptProxy({
object: bridge,
name: "nativeBridge", // H5 侧通过 window.nativeBridge 访问
methodList: ["callNative"], // 方法白名单
controller: webController
})
H5 侧调用:
window.nativeBridge.callNative("func", JSON.stringify({a: 1}));
还有两种等价写法:
-
registerJavaScriptProxy(object, name, methodList)— 通过 controller 动态注册/注销 -
deleteJavaScriptRegister(name)— 注销代理
4. onLoadIntercept(URL 拦截)
H5 通过 window.location.href 跳转特定协议,原生侧拦截解析。
Web({ src: $url, controller: webController })
.onLoadIntercept((event: OnLoadInterceptEvent) => {
let url = event.data.getRequestUrl();
if (url.startsWith("myapp://")) {
// 解析协议,处理原生逻辑
return true; // 拦截,不加载
}
return false;
})
H5 侧:
window.location.href = "myapp://action?data=xxx";
// 或
document.location = "myapp://action?data=xxx";
5. onInterceptRequest(资源请求拦截)
比 onLoadIntercept 更底层,可拦截所有资源请求(Ajax、图片等),可自定义返回内容。
Web({ src: $url, controller: webController })
.onInterceptRequest((event: OnInterceptRequestEvent) => {
let request = event.request;
if (request.getRequestUrl().includes("custom-api://")) {
// 返回自定义响应,实现伪协议通信
event.setResponse(new WebResourceResponse(...));
}
return event.getResponse();
})
6. onAlert / onConfirm / onPrompt
H5 调用 alert() / confirm() / prompt() 时原生侧可捕获,常用于轻量级数据传递。
Web({ src: $url, controller: webController })
.onAlert((event: OnAlertEvent) => {
let message = event.message; // H5 传入的数据
// 处理逻辑
event.result?.handleConfirm(); // 关闭弹窗
return true; // 已处理,不显示系统默认弹窗
})
H5 侧:
alert(JSON.stringify({action: "share", data: {...}}));
7. onConsole
H5 的 console.log/warn/error/debug/info 会被原生侧的 onConsole 捕获,适合调试日志收集。
Web({ src: $url, controller: webController })
.onConsole((event: OnConsoleEvent) => {
let logMsg = event.message.getMessage();
let level = event.message.getLogLevel();
// 原生日志系统记录
return true;
})
三、双向 / 混合
8. 消息端口(MessagePort)
通过 postMessage 机制实现双向通信,类似 Chrome 扩展的消息通道。
// 原生侧
let ports: MessagePort[] = [];
// 创建消息通道
webController.createWebMessagePorts(ports);
webController.postMessage("msgKey", [ports[0]], "*");
ports[0].onMessageEvent((result: WebMessage) => {
// 收到 H5 消息
let data = result.getData();
});
通信机制
原生侧 H5 侧
① createWebMessagePorts()
② postMessage 将 port 发送给 H5 ──────→ ③ window.onmessage 收到 port
④ port.onMessageEvent 监听 ←── ⑤ port.postMessage("data") 发送
⑥ port.postMessage("reply") ────────→ ⑦ port.onmessage 收到回复
原生侧(ArkTS):
@Component
struct WebPostMessageDemo {
controller: WebviewController = new webview.WebviewController();
ports: MessagePort[] = [];
aboutToAppear() {
// ① 创建消息端口
this.ports = this.controller.createWebMessagePorts();
// ④ 监听 H5 发来的消息
this.ports[0].onMessageEvent((result: WebMessage) => {
let data = result.getData(); // 接收到的数据
console.log('收到 H5 消息:', data);
// ⑥ 回复消息给 H5
this.ports[0].postMessageEvent(
JSON.stringify({ type: 'reply', msg: '原生已收到' })
);
});
}
build() {
Web({ src: "https://example.com", controller: this.controller })
.onPageEnd(() => {
// ② 页面加载完成后,将 port 发送给 H5——这个port 自带一个单独的槽位
// 或者通过在页面上onmessage发送到H5端(这边是原生这边通过这种方式发送)
this.controller.postMessage('init_port', [this.ports[0]], '*');
})
}
}
H5 侧(JavaScript):
// ③ 接收原生发来的端口
window.addEventListener('message', (event) => {
if (event.data === 'init_port' && event.ports.length > 0) {
let nativePort = event.ports[0]; // 获得通信端口
// ⑦ 监听原生回复
nativePort.onmessage = (e) => {
console.log('收到原生回复:', e.data);
};
// ⑤ 发送消息给原生
nativePort.postMessage(JSON.stringify({
action: 'getUserInfo',
params: { userId: 123 }
}));
}
});
与 javaScriptProxy 的对比
| 特性 | MessagePort | javaScriptProxy |
|---|---|---|
| 通信方向 | 双向 | H5→原生为主 |
| 异步/同步 | 异步 | 同步返回 |
| 数据类型 | 字符串 | 方法参数(序列化) |
| 多通道 | 支持多个 port | 单一代理对象 |
| 性能 | 高,适合高频 | 一般 |
| 使用复杂度 | 较复杂 | 简单 |
| 适用场景 | 高频双向通信 | H5 调原生能力 |
关键 API 说明
| API | 所在侧 | 作用 |
|---|---|---|
| createWebMessagePorts() | 原生 | 创建端口数组 |
| postMessage(name, ports, uri) | 原生 | 将端口发送到 H5 的 window.onmessage |
| port.onMessageEvent(callback) | 原生 | 监听 H5 发来的消息 |
| port.postMessageEvent(data) | 原生 | 向 H5 发送消息 |
| port.close() | 原生 | 关闭端口 |
| window.onmessage | H5 | 接收原生发来的端口 |
| port.postMessage(data) | H5 | 向原生发送消息 |
| port.onmessage | H5 | 监听原生发来的消息 |
9. 生命周期事件
通过 Web 组件的事件回调感知 H5 页面状态:
| 事件 | 说明 |
|---|---|
onPageBegin |
页面开始加载 |
onPageEnd |
页面加载完成 |
onErrorReceive |
加载出错 |
onTitleReceive |
标题变化 |
onScroll |
页面滚动 |
onRenderExited |
渲染进程退出 |
onDownloadStart |
下载开始 |
总结对比
| 方式 | 方向 | 可靠性 | 适用场景 |
|---|---|---|---|
| javaScriptProxy | H5→原生 | ★★★★★ | H5调用原生能力(推荐首选) |
| runJavaScript | 原生→H5 | ★★★★★ | 原生通知/调用H5(推荐首选) |
| javaScriptOnDocumentStart | 原生→H5 | ★★★★ | 初始化注入 |
| onLoadIntercept | H5→原生 | ★★★★ | URL Scheme跳转拦截 |
| onInterceptRequest | H5→原生 | ★★★★ | 自定义资源/伪协议 |
| onAlert/Confirm/Prompt | H5→原生 | ★★★ | 轻量数据传递(有性能开销) |
| onConsole | H5→原生 | ★★★ | 日志收集 |
| MessagePort | 双向 | ★★★★★ | 高频双向消息通道 |
最佳实践
主力使用 javaScriptProxy + runJavaScript,复杂双向场景配合 MessagePort,辅助使用 URL 拦截处理特殊协议路由。
Web 前端视角:与原生(App)交互方式
一、Android(WebView)
| H5 调用方式 | 说明 |
|---|---|
window.NativeObject.method() |
原生注入的 JS 对象,直接调用其方法 |
window.location.href = "scheme://..." |
URL Scheme 跳转拦截 |
prompt("data") / alert("data")
|
弹窗拦截,不推荐 |
| H5 被调方式 | 说明 |
|---|---|
| 原定注入到页面中的变量/函数 | 原生通过 evaluateJavascript 或 loadUrl("javascript:...") 调用 H5 函数 |
二、iOS(WKWebView)
| H5 调用方式 | 说明 |
|---|---|
window.webkit.messageHandlers.xxx.postMessage(data) |
向原生注册的 handler 发送消息 |
window.location.href = "scheme://..." |
URL Scheme 拦截 |
prompt("data") / alert("data")
|
弹窗拦截,不推荐 |
| H5 被调方式 | 说明 |
|---|---|
| 页面中暴露的函数 | 原生通过 evaluateJavaScript 调用 |
| 注入到全局变量/函数 | 原生通过 WKUserScript 预注入 |
| H5 发起请求 | 说明 |
|---|---|
fetch("customscheme://...") |
自定义协议请求,原生通过 WKURLSchemeHandler 拦截 |
三、ArkWeb(鸿蒙)
| H5 调用方式 | 说明 |
|---|---|
window.bridgeName.method() |
原生通过 javaScriptProxy 注入的对象,H5 直接调用 |
window.location.href = "scheme://..." |
URL Scheme 拦截 |
prompt("data") / alert("data")
|
弹窗拦截,不推荐 |
| H5 被调方式 | 说明 |
|---|---|
| 页面中暴露的函数 | 原生通过 runJavaScript 调用 |
| 注入到全局变量/函数 | 原生通过 javaScriptOnDocumentStart 预注入 |
window.onmessage 接收 MessagePort |
原生通过 postMessage 发送端口,建立双向通道 |
四、跨平台通用总结
从 H5 角度看,无论哪个平台,本质就是这几招:
| 方式 | 平台差异 |
|---|---|
| 全局对象直接调 | Android: window.xxx / iOS: messageHandlers.xxx / 鸿蒙: window.xxx
|
| URL Scheme 跳转 | 三平台通用 |
| 弹窗拦截 | 三平台通用(不推荐) |
| 被原生调 | 暴露函数即可,三平台通用 |
| MessagePort 双向 | 鸿蒙独有 |
核心就一句话:H5 不管平台细节,封装一个统一 JSBridge 层,底层适配不同平台的调用方式即可。