HarmonyOS 鸿蒙NEXT实现H5 JSBridge方案
HarmonyOS 鸿蒙NEXT实现H5 JSBridge方案
背景
通常在移动端项目中涉及到H5与Native的通信,基本的通用方案是通过客户端去注入JS到网页中,然后拦截H5发起的自定义scheme请求来实现通信,当然还有在iOS的webkit通过WKScriptMessageHandler协议来实现与JS交互,它允许应用程序从js接收postMessage消息,通过执行evaluateJavaScript回传消息到H5,但这种方式不再支持UIWebView,所以我们这里对此种方式不做赘述,只对通过使用 iframe 创建消息队列通信方式进行介绍。
常用三方库
开源框架:WebViewJavascriptBridge、Jockey
本文是对H5和原生侧各自实现Jockey的介绍,包括代码层面的实现细节以及在鸿蒙项目中的具体应用场景和用法。
H5侧实现
首先向window中添加一个Jockey对象,对象内部初始化消息队列来实现双端通信
Jockey.dispatchers.push(nativeDispatcher);
Jockey.dispatchers.push(IframeDispatcher);
window.addEventListener("message", $.proxy(Jockey.onMessageRecieved, Jockey), false);
window.Jockey = Jockey;
在H5侧实现中通过监听和发送消息来进行H5向Native发起请求和接收Native消息的处理,概括为下面两种方式:
Jockey.on('getInfoCallback', off);
Jockey.send('getInfo', params);
下面分开介绍具体实现,首先是Jockey中H5接受Native回调:
var Jockey = {
listeners: {}, // H5侧维护的消息队列
messageCount: 0, // 用来发送和接受native回调的标识符
// 监听来自native的发送消息,type为具体事件,fn为回调函数
// H5会将所有需native侧回调的事件放入listeners队列中
on: function(type, fn) {
},
// 接受来自native的回调,并触发listeners监听事件回调H5,
// 当此type的所有回调执行完成后调用complete通知native(调用的是下列nativeDispatcher中sendCallback方法)
trigger: function(type, messageId, json) {
}
}
H5向Native发起请求
Jockey中H5向Native发起请求部分
同时messageCount作为后续接受native回调callbacks的标识符,dispatcher.send最终会调用到nativeDispatcher中的send方法
send: function(type, payload, complete) {
if (payload instanceof Function) {
complete = payload;
payload = null;
}
payload = payload || {};
complete = complete || function() {};
var envelope = this.createEnvelope(this.messageCount, type, payload);
this.dispatchers.forEach(function(dispatcher) {
dispatcher.send(envelope, complete);
});
this.messageCount += 1; // 每发送一次消息加1
},
创建 iframe
nativeDispatcher实现,主要是向native发送消息和接受回调的逻辑:
var nativeDispatcher = {
callbacks: {},
// 向native主动发起通信,envelope是消息体,complete会存在H5侧维护的消息队列中
send: function(envelope, complete) {
this.dispatchMessage("event", envelope, complete);
},
},
// 发送消息核心实现
dispatchMessage: function(type, envelope, complete) {
var src = "jockey://" + ...
var iframe = document.createElement("iframe");
iframe.setAttribute("src", src);
document.documentElement.appendChild(iframe);
iframe.parentNode.removeChild(iframe);
iframe = null;
}
}
上述代码中主要有两点:
- H5向Native发送消息,是通过创建 iframe 发起了一个src请求,特殊的是请求的URL自定义了scheme(jockey://)用来客户端区分请求类型进行拦截。
- H5在发送和接受消息时维护了listeners和callbacks两个消息队列,分别用来接受Native侧主动发送的消息和回调
之后就是Native拦截自定义scheme请求以及向H5发送请求,实现Native向H5通信并接收H5消息回传。
鸿蒙侧实现
清楚了H5侧的实现,在鸿蒙侧也只需要按流程重新实现一边即可,需要关注的是如何拦截H5请求以及如何向H5发送消息。
// 监听H5回调
onCallback() {
this.Jockey?.on('getInfo, {
payload: () => {
}
jockeyRetrun: () => {
let params: string = JSON.stringify(dataParams)
return params
}
})
}
sendPfo(perform?: (returnStr: string) => void) {
let params: string = JSON.stringify(dataParams)
this.Jockey?.send('getInfoCallBack', params, (returnStr) => {
console.log(returnStr)
if (returnStr) {
perform?.(returnStr)
}
})
}
Jockey.on 会在Jockey内添加listeners监听消息队列,来接受来自H5的回调
public on(type: string, handler: JockeyAsyncHandler) {
let listenerList = this.listeners.get(type)
if (listenerList) {
listenerList.push(handler)
} else {
listenerList = [handler]
this.listeners.set(type, listenerList)
}
}
Jockey.send 触发Jockey内WebviewController执行runJavaScriptExt脚本来调用H5内trigger函数,
传递的参数包括type事件、messageId以及消息数据,其中messageId是维护在Native侧来接受H5回调的标识符,保存在callbacks消息队列中
triggerCallbackForMessage是拦截H5请求后,触发callbacks消息队列接受H5侧消息回传,至此已完成Native向H5通信并接受H5消息回传。
trigger实现:
public send(type: string, params: string, perform?: (returnStr: string) => void) {
let messageId = this.messageCount.toString()
if (perform) {
this.callbacks.set(messageId.toString(), perform)
}
let js = ``
try {
// 异步执行JavaScript脚本,并通过Promise方式返回脚本执行的结果
this.controller.runJavaScriptExt(
js,
(error, result) => {
if (error) {
let e: business_error.BusinessError = error as business_error.BusinessError
console.error(`ErrorCode: ${e.code}, Message: ${e.message}`)
return
}
this.messageCount += 1
if (result) {}
})
} catch (error) {
let e: business_error.BusinessError = error as business_error.BusinessError
console.error(`ErrorCode: ${e.code}, Message: ${e.message}`)
}
}
// 拦截H5请求后,触发callbacks消息队列接受H5给Native回调
private triggerCallbackForMessage(messageId: string, params: string) {
}
WebComponent
其中WebComponent是通用的web容器,外部调用方式也很简单:
WebComponent({webUrl: this.webUrl})
组件内部会自动绑定DUJockey到当前web,通过WebviewController可以控制Web组件各种行为,
一个WebviewController对象只能控制一个Web组件,且必须在Web组件和WebviewController绑定后,才能调用WebviewController上的方法,我们后续想H5发送消息就需要通过WebviewController来实现。
private webviewController: web_webview.WebviewController = new web_webview.WebviewController()
aboutToAppear(): void {
this.duJockey = new DUJockey(this.webviewController)
}
WebComponent内部会加载web组件并管理其生命周期,在这里实现web各种加载状态和回调。
其中onLoadIntercept
就是Web组件加载url时触发的回调,用于判断是否阻止此次访问
相当于iOS decidePolicyForNavigationAction
和Android的 shouldOverrideUrlLoading
拦截请求方法,在这里根据scheme判断是否是来自Jockey请求
onLoadIntercept
方法返回 return 为ture表示拦截成功,不执行后面的跳转操作。而false表示按正常流程执行。拦截成功后我们从url中获取data,接着就可以按照我们自己的需求去处理了。
public hookUrl(url: string): boolean {
}
如果在Native侧listeners有对应的消息监听,会触发消息回传到Native,
同时如果Native需要回调结果给H5,在接收了该事件下所有H5消息后会触发triggerCallbackOnWebView回调到H5
private triggerEventFromWebView(params: string) {
}
到这里已实现Native接受来自H5的消息并回调H5,整个流程全部跑通。
更多关于HarmonyOS 鸿蒙NEXT实现H5 JSBridge方案的实战教程也可以访问 https://www.itying.com/category-93-b0.html
鸿蒙NEXT实现H5 JSBridge方案步骤:
- 使用鸿蒙的Web组件加载H5页面
- 通过@ohos.web.webview提供的能力注册JavaScriptProxy对象
- H5调用Native方法使用window.harmonyWebview.postMessage()
- Native调用H5方法使用webController.runJavaScript()
- 双向通信需在Web组件配置fileAccess和javaScriptProxy属性
关键代码示例:
// 注册JSBridge
webview.javaScriptProxy = {
object: {
nativeMethod: (arg) => {
// 处理H5调用
}
}
}
// H5调用方式
window.harmonyWebview.postMessage({method:'nativeMethod', data:'arg'})
更多关于HarmonyOS 鸿蒙NEXT实现H5 JSBridge方案的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html
在HarmonyOS Next中实现H5与Native通信的JSBridge方案,核心思路与主流方案一致,主要通过Webview拦截自定义scheme实现双向通信。
- H5侧实现:
- 使用iframe发送jockey://scheme请求,通过window.postMessage实现消息传递
- 维护listeners队列处理Native回调,callbacks队列处理异步响应
- 典型API设计:Jockey.on()注册监听,Jockey.send()发起请求
- Native侧关键实现:
// 拦截Webview请求
onLoadIntercept(event: { url: string }): boolean {
if (event.url.startsWith('jockey://')) {
this.handleJockeyRequest(event.url)
return true // 拦截请求
}
return false
}
// 执行JS回调H5
sendToH5(type: string, data: string) {
const js = \`window.Jockey.trigger("\${type}", \${data})\`
this.webviewController.runJavaScript(js)
}
- 鸿蒙特有API:
- WebviewController提供runJavaScript方法执行H5脚本
- onLoadIntercept回调拦截自定义scheme请求
- 通过@ohos.web.webview模块实现Web能力
注意事项:
- 消息协议建议采用JSON格式统一数据格式
- 需要处理Webview的生命周期绑定问题
- 对于复杂数据类型需要序列化处理
这种方案在鸿蒙上的性能表现优于传统iframe方案,因鸿蒙对Webview有深度优化。实际开发时可结合具体业务需求扩展协议字段。