鸿蒙操作系统使用 ArkWeb 实现 H5 与原生 JSBridge 双向通信实战
一、背景说明
混合开发是鸿蒙应用承接存量 Web 业务的主流方案:H5 页面负责快速迭代的业务界面,原生侧提供设备能力与性能敏感模块,两者之间靠 JSBridge 通信。ArkWeb 的 Chromium 内核在 26.0.0 版本已从 132 升级到 144,能力持续跟进,而应用侧通信入口始终是 @ohos.web.webview 的 WebviewController。本文完整实现"H5 调用原生方法(registerJavaScriptProxy)+ 原生调用 H5 函数(runJavaScript)"的双向 JSBridge,代码可直接运行。
二、技术原理
ArkWeb 提供两条通信通道,方向相反、配合使用:
- H5 → 原生(JavaScriptProxy):应用把一个 ArkTS 对象注入到页面的 window 上,H5 直接以
window.对象名.方法()调用,参数与返回值支持 string / number / boolean 及可嵌套的 Dictionary、Array。注入方式有两种:Web 组件初始化时用javaScriptProxy()属性,或初始化后用registerJavaScriptProxy()(需在下一次页面加载后生效); - 原生 → H5(runJavaScript):在当前页面上下文异步执行任意 JS 脚本,通过 Promise 拿返回值,常用于把原生计算结果回推给页面。
数据流为:Web 组件绑定 WebviewController → onControllerAttached 时 registerJavaScriptProxy 注入桥对象 → 页面加载完成后 H5 调用 window.bridge.getTitle() → 原生执行后用 runJavaScript 回调 window.onNativeResult(data) 通知页面。
三、实现步骤
无需任何权限。工程要求 Stage 模型、API 12 及以上(含 26.0.0)。先准备本地测试页 src/main/resources/rawfile/index.html(完整代码):
<!DOCTYPE html>
<html>
<head><meta charset="utf-8"><title>JSBridge Demo</title></head>
<body>
<h3 id="status">等待调用...</h3>
<button onclick="callNative()">调用原生 getDeviceInfo</button>
<script>
// 原生侧通过 runJavaScript 回调此函数
function onNativeResult(data) {
document.getElementById('status').innerText = '原生返回:' + data;
}
// H5 主动调用注入的原生对象
function callNative() {
var r = window.bridge.getDeviceInfo();
document.getElementById('status').innerText = 'H5 拿到:' + r;
}
</script>
</body>
</html>
新建 src/main/ets/pages/Index.ets(完整代码,双向通信):
import { webview } from '@kit.ArkWeb';
import { BusinessError } from '@kit.BasicServicesKit';
import { deviceInfo } from '@kit.BasicServicesKit';
// 注入给 H5 的桥对象:方法参数与返回值需为 string/number/boolean 或可序列化结构
class JsBridge {
getDeviceInfo(): string {
return JSON.stringify({
osFullName: deviceInfo.osFullName,
sdkApiVersion: deviceInfo.sdkApiVersion
});
}
// 原生耗时任务示例:H5 触发后由原生回推结果
startTask(): void {
setTimeout(() => {
try {
Index.getInstance().controller.runJavaScript(
`onNativeResult('task done')`);
} catch (err) {
const e: BusinessError = err as BusinessError;
console.error('runJavaScript failed: ' + e.code + ' ' + e.message);
}
}, 1000);
}
}
@Entry
@Component
struct Index {
controller: webview.WebviewController = new webview.WebviewController();
private bridge: JsBridge = new JsBridge();
private static inst: Index | null = null;
static getInstance(): Index { return Index.inst as Index; }
aboutToAppear(): void { Index.inst = this; }
build() {
Column() {
Web({ src: $rawfile('index.html'), controller: this.controller })
.javaScriptAccess(true)
.onControllerAttached(() => {
try {
// 注册同步方法列表;结束后需 deleteJavaScriptRegister 防泄漏
this.controller.registerJavaScriptProxy(
this.bridge, 'bridge', ['getDeviceInfo', 'startTask']);
} catch (err) {
const e: BusinessError = err as BusinessError;
console.error('registerJavaScriptProxy failed: ' + e.code);
}
})
.width('100%').height('100%')
Button('原生调 H5:读取页面标题')
.onClick(() => {
this.controller.runJavaScript('document.title')
.then((r: string) => console.info('page title: ' + r))
.catch((err: BusinessError) => console.error('err: ' + err.code));
})
}
}
}
运行后点击页面按钮即完成一次"H5 → 原生 → H5"的往返调用;应用底部按钮演示原生主动读取页面状态。
四、应用场景示例
实际业务中常在 H5 加载完成后由原生下发登录态:
Web({ src: 'https://example.com/home', controller: this.controller })
.javaScriptAccess(true)
.onPageEnd(() => {
this.controller.runJavaScript(
`window.setToken('${this.token}')`)
.then(() => console.info('token injected'))
.catch((err: BusinessError) => console.error('err: ' + err.code));
})
五、技术要点
- 注入生效时机:registerJavaScriptProxy 注入的对象在下一次页面加载后才出现在 window 上,务必放在
onControllerAttached里注册再 loadUrl,或注册后调用refresh(); - 内存泄漏:registerJavaScriptProxy 必须与
deleteJavaScriptRegister('bridge')配对使用,页面销毁时注销; - 同步与异步方法列表:两个列表不可同时为空;同一方法重复注册时默认按异步调用;异步方法无法取返回值,需要返回值的方法放入同步列表;
- 安全白名单:registerJavaScriptProxy 支持
permission参数(JSON 字符串),可配置 object / method 级别的 scheme+host URL 白名单,防止不可信页面调用桥对象;仅在可信 HTTPS 场景注册; - 数据类型限制:桥方法参数/返回值不支持任意对象,复杂结构用 JSON 字符串序列化;runJavaScript 的返回值是 JSON 字符串,需
JSON.parse反序列化; - 页面导航丢状态:跨导航后 JS 状态不保留,注入对象不受影响,这也是推荐用 registerJavaScriptProxy 而非 runJavaScript 维持状态的原因。
六、扩展建议
若需要高频、消息式通信(如直播弹幕、传感器数据流),改用 WebMessagePort 建立持久双通道:
let ports = this.controller.createWebMessagePorts();
this.controller.postMessage({ type: 'port', data: 'init' }, 'index.html', [ports[0]]);
ports[1].onMessageEvent((msg: webview.WebMessage) => {
console.info('from h5: ' + msg.getString());
});
ports[0] 发给页面后,H5 通过 onmessage 监听即可双向收发,避免每次调用都走注入链路。
结语
本文用约 100 行 ArkTS 加一个测试 HTML 实现了 ArkWeb 上完整的 JSBridge:注入桥对象让 H5 直接调用原生能力,runJavaScript 让原生回推结果,并覆盖注入时机、内存泄漏、权限白名单三个最容易踩的坑。这套模式可直接作为混合开发项目的通信基座。
更多推荐




所有评论(0)