鸿蒙Web与JSBridge使用
前言
最近好久好久没写文章了,又谈了一场失败的恋爱,又是帮请假一个月的同事干活,又是搞鸿蒙,学习写文章的激情下降的比较厉害,这几天打算收一收,写点鸿蒙的文章,做个记录吧。
我这也不想写鸿蒙的教程,只是把开发过程中我觉得有必有记录的东西写一写,而且基本上算新手,内容也比较讲的。
这篇文章就来写下Web与JSBridge使用,因为我们这APP就是套壳H5的。
官方文档
首先还是看官方文章吧,网上关于鸿蒙的文章还是比较少,尽量看API吧,虽然也要授权才能看:
Webview的使用
下面就用个最简单的例子看下鸿蒙Webview的使用:
// xxx.ets
import web_webview from '@ohos.web.webview';
@Entry
@Component
struct WebComponent {
controller: web_webview.WebviewController = new web_webview.WebviewController();
build() {
Column() {
// 组件创建时,通过$rawfile加载本地文件local.html
Web({ src: $rawfile("local.html"), controller: this.controller })
.onPageEnd((event) => {
if (event) {
}
})
.domStorageAccess(true)
.javaScriptAccess(true)
.fileAccess(true)
.geolocationAccess(true)
.domStorageAccess(true)
.onlineImageAccess(true)
}
}
}
这里就是一个Web组件加载本地html,"local.html"放在resources的rawfile目录下。
Web组件还需要传入一个WebviewController,它负责处理Web组件的一些功能,Web组件还可以直接设置WebAttribute,类似安卓的WebSettings吧。这里好像没有WebViewClient和WebChromeClient那样的东西。
JSBridge使用
注册JSBridge
这里JSBridge有两种写法,一个是在Web组件上设置:
// xxx.ets
import web_webview from '@ohos.web.webview';
class testClass {
constructor() {
}
test(): string {
return 'ArkTS Hello World!';
}
}
@Entry
@Component
struct WebComponent {
webviewController: web_webview.WebviewController = new web_webview.WebviewController();
// 声明需要注册的对象
@State testObj: testClass = new testClass();
build() {
Column() {
// web组件加载本地index.html页面
Web({ src: $rawfile('index.html'), controller: this.webviewController})
// 将对象注入到web端
.javaScriptProxy({
object: this.testObj,
name: "testObjName",
methodList: ["test"],
controller: this.webviewController
})
}
}
}
另一个就是通过WebviewController设置,下面丢一段官方例子吧:
// xxx.ets
import web_webview from '@ohos.web.webview';
import business_error from '@ohos.base';
class testClass {
constructor() {
}
test(): string {
return "ArkUI Web Component";
}
toString(): void {
console.log('Web Component toString');
}
}
@Entry
@Component
struct Index {
webviewController: web_webview.WebviewController = new web_webview.WebviewController();
@State testObj: testClass = new testClass();
build() {
Column() {
Button('refresh')
.onClick(() => {
try {
this.webviewController.refresh();
} catch (error) {
let e: business_error.BusinessError = error as business_error.BusinessError;
console.error(`ErrorCode: ${e.code}, Message: ${e.message}`);
}
})
Button('Register JavaScript To Window')
.onClick(() => {
try {
this.webviewController.registerJavaScriptProxy(this.testObj, "testObjName", ["test", "toString"]);
} catch (error) {
let e: business_error.BusinessError = error as business_error.BusinessError;
console.error(`ErrorCode: ${e.code}, Message: ${e.message}`);
}
})
Web({ src: $rawfile('index.html'), controller: this.webviewController })
}
}
}
区别就是Web组件上只能注册一个JSBridge,而webviewController上可以注册多个,不过有个坑就是:
使用registerJavaScriptProxy()接口注册方法时,注册后需调用refresh()接口生效。
这个就很迷,所以我选择了简单又方便的Web组件注册JSBridge。
方法调用
这里建议看下这个官方文档吧,里面写的挺清楚的:
我这一般就用两种形式的方法调用,一种是返回Promise,一种是传入回调,这样写可以在方法里面做一些耗时操作,有兴趣的可以参考下:
class UtilsBridge {
/** 页面上下文 **/
context: Context = null!
// 单次调用示例
singleCall(args: Array<string | number | boolean | object>): Promise<string> {
return new Promise((resolve, reject) => {
try {
let str: string = args.get(0) as string;
} catch (e) {
LogUtil.e(e)
reject(e)
}
});
}
// 多次调用示例
multiCall(args: Array<string | number | boolean | object>, resolve: Function, reject: Function) {
}
}
export default UtilsBridge
在html里面调用:
function singleCall() {
harmony.singleCall([ ]).then(res => {
}).catch(error => {
});
}
function multiCall() {
harmony.startPcmRecord(["test"], function(param){
},function(param){
});
}
如果JSBridge的方法需要用到context,可以通过下面方式传递进去(不知道会不会内存泄漏-_-||):
Web({ src: $rawfile('index.html'), controller: this.webviewController})
.onPageEnd((event) => {
if (event) {
// 向UtilsBridge传入context
this.utilsBridge.context = getContext(this)
}
})
Message传递
除了JSBridge外,我这常用的还有就是Message传递了,html可以通过注册listener来监听原生消息,下面封装了一个类:
import { BusinessError } from '@kit.BasicServicesKit';
import { webview } from '@kit.ArkWeb';
import LogUtil from '../utils/LogUtil'
/**
* 消息通知类,向H5发送message
*
* @author lfq 2024-06-24
*/
class MessageBridge {
constructor() {}
// 和H5通信的Ports
private ports: webview.WebMessagePort[] = null!
/**
* 创建和H5通信的Ports
*
* @param controller webview控制器
*/
createPorts(controller: webview.WebviewController) {
try {
// 1、创建两个消息端口。
let ports = controller.createWebMessagePorts();
this.ports = ports
// 2、在应用侧的消息端口(如端口1)上注册回调事件。
ports[1].onMessageEvent((result: webview.WebMessage) => {
if (typeof (result) == "string") {
LogUtil.d("onMessageEvent:" + result);
} else {
LogUtil.d("not support");
}
})
// 3、将另一个消息端口(如端口0)发送到HTML侧,由HTML侧保存并使用。
controller.postMessage('__init_port__', [ports[0]], '*');
// 4、使用应用侧的端口给另一个已经发送到html的端口发送消息。
// ports[1].postMessageEvent('onDeviceReady');
} catch (error) {
LogUtil.e(`ErrorCode: ${(error as BusinessError).code}, Message: ${(error as BusinessError).message}`);
}
}
/**
* 向webview发送onDeviceReady事件
*/
onDeviceReady() {
if (this.ports && this.ports[1]) {
this.ports[1].postMessageEvent('onDeviceReady');
}
}
/**
* 向webview发送resume事件
*/
resume() {
if (this.ports && this.ports[1]) {
this.ports[1].postMessageEvent('resume');
}
}
/**
* 向webview发送pause事件
*/
pause() {
if (this.ports && this.ports[1]) {
this.ports[1].postMessageEvent('pause');
}
}
/**
* 向webview发送online事件
*/
online() {
if (this.ports && this.ports[1]) {
this.ports[1].postMessageEvent('online');
}
}
/**
* 向webview发送offline事件
*/
offline() {
if (this.ports && this.ports[1]) {
this.ports[1].postMessageEvent('offline');
}
}
/**
* 主动调用,释放资源
*/
onDestroy() {
}
}
export default MessageBridge;
我这给H5发送了网页加载完成、页面显示、隐藏、网络联网、断网几个消息,还是挺有用的,看下Index里的写法:
// xxx.ets
import web_webview from '@ohos.web.webview';
class testClass {
constructor() {
}
test(): string {
return 'ArkTS Hello World!';
}
}
@Entry
@Component
struct Index {
controller = new webView.WebviewController();
@State messageBridge: MessageBridge = new MessageBridge();
@State utilsBridge: UtilsBridge = new UtilsBridge();
currentNet = connection.createNetConnection()
// 页面即将显示时调用
onPageShow() {
this.messageBridge.resume()
}
// 页面即将消失时调用
onPageHide() {
this.messageBridge.pause()
}
build() {
Column() {
// web组件加载本地index.html页面
Web({ src: $rawfile('index.html'), controller: this.webviewController})
.onPageEnd((event) => {
if (event) {
// 发送onDeviceReady事件
LogUtil.d('onPageEnd:' + event.url);
this.messageBridge.createPorts(this.controller)
this.messageBridge.onDeviceReady()
// 向UtilsBridge传入context
this.utilsBridge.context = getContext(this)
// 开启网络监听
this.startListenNetwork()
}
})
}
}
// 启动网络监听
private startListenNetwork() {
this.currentNet.register((error) => {
LogUtil.d(error ? "开启监听失败" : "开启监听成功")
})
this.currentNet.on("netAvailable", () => {
LogUtil.d("netAvailable")
this.messageBridge.online()
this.utilsBridge.netWorkState = 0
})
this.currentNet.on("netLost", () => {
LogUtil.d("netLost")
this.messageBridge.offline()
this.utilsBridge.netWorkState = 1
})
// 实测不生效
this.currentNet.on("netUnavailable", () => {
LogUtil.d("netUnavailable")
this.messageBridge.offline()
this.utilsBridge.netWorkState = 1
})
}
}
这样就能在html里面监听了:
//test.js
var h5Port;
var output = document.querySelector('.output');
window.addEventListener('message', function (event) {
if (event.data == '__init_port__') {
if (event.ports[0] != null) {
h5Port = event.ports[0];
// 1. 保存从ets侧发送过来的端口
h5Port.onmessage = function (event) {
// 2. 接收ets侧发送过来的消息.
var msg = '';
var result = event.data;
if (typeof(result) == "string") {
msg = msg + result;
} else {
console.log("not support");
}
output.innerHTML = output.innerHTML + msg + '<Br/>';
}
}
}
})
// 3. 使用h5Port往ets侧发送消息.
function PostMsgToEts(data) {
if (h5Port) {
h5Port.postMessage(data);
} else {
console.error("h5Port is null, Please initialize first");
}
}
比较坑的一点就是addEventListener不能直接监听我们发送的"online"、"offline"这样的消息,还得h5Port.onmessage处理下。
小结
简单整理了下鸿蒙里面Web的使用、JSBridge使用以及postMessage的使用,比较初级,算是记录吧。