背景
当公司有一批产品矩阵的时候,我们在做基础的har包构建的时候也是希望把webview进行组件化的,考虑到老的项目端和h5经常有非常丰富的交互jsBridge,那么如何优雅的把webview封装到har包中,而不造成和业务视图或者逻辑的耦合呢?答案是虚类,ArkTs是支持面向对象的,因此我们可以根据虚类的方式,让jsbridge类在业务代码中实现,而在webview初始化的时候将jsbridge的对象实例化并在特定时刻注入进去。
实现
- 如何进行jsbridge的实现,这个官方有文档,大家可以自行去搜索,这里展示一下代码
typescript
Web({ src: "", controller: this.controller })
.domStorageAccess(true)
.javaScriptAccess(true)
.zoomAccess(false)
.onControllerAttached(() => {
//注册代理
if (this.diyJSExportObject) { this.controller.registerJavaScriptProxy(this.diyJSExportObject.getInstance(this.pathStack,this.controller), "diyJSExportObject", ["jsCallback"])
}
// 进入时初始化
this.controller.loadUrl(this.url);
})
可以看到,我们通过webcontroller的registerJavaScriptProxy方法为webview环境注入了一个全局对象diyJSExportObject,这个对象只有一个方法jsCallback,然后我们所有的客户端和h5的交互都通过window.diyJSExportObject.jsCallback进行交互,因此这里需要和webview端做一些约定,具体如何约定可以参考之前的一篇文章Vue3桥接composition api
- 如何声明和定义diyJSExportObject
首先我们想要解耦,那么这个对象的实例化,肯定不能在har包中,因此我们需要通过参数将实例化对象传入,但是我们不同的业务可能命名的类都不一样,而且类也不能从业务进行引入,因此我们就采用了虚基类的方法进行声明和对象化。
typescript
diyJSExportObject: DIYJSExportObject | null = null;
- DIYJSExportObject的实现
显然我们需要实现一个jsCallback方法以供web测调用,然后要实现一个端测调用web测的方法,可以根据上面代码看到我们需要进行pathStack和controller的传递,因此DIYJSExportObject的实现要至少有这两个参数,然后因为实例需要传入pathStack和controller,我们不能通过构造函数直接获取,因此需要提供一个动态获取实例的方法getInstance,那么DIYJSExportObject的实现即可构建为:
typescript
export abstract class DIYJSExportObject {
abstract pathStack: NavPathStack;
abstract controller: webview.WebviewController;
// 端测调用web测的方法
abstract nativeCallJs(actionName: string, data: CommonObj): void;
abstract jsCallback(callee: string): void;
abstract getInstance(pathStack: NavPathStack, controller: webview.WebviewController): DIYJSExportObject
}
- DIYJSExportObject的实现类,业务侧
业务侧我们将进行DIYJSExportObject的实现,该类可以进行一些前后端的交互,比如互相调用方法,互相调用组件,支持webview测进行分享,图片保存,缓存等等其他高端的端侧功能,一般的我们会约定不同的id来对应不同的功能:
typescript
export class TESTJSExportObject extends DIYJSExportObject {
pathStack: NavPathStack;
controller: webview.WebviewController;
constructor() {
super()
this.pathStack = new NavPathStack();
this.controller = new webview.WebviewController;
}
getInstance(pathStack: NavPathStack, controller: webview.WebviewController) {
this.pathStack = pathStack;
this.controller = controller;
return this
}
nativeCallJs(funName: string, data: CommonObj) {
this.controller.runJavaScript(
`${funName}(${JSON.stringify(JSON.stringify(data))})`,
(error, result) => {
if (error) {
console.error(`run JavaScript error, ErrorCode: ${(error as BusinessError).code}, Message: ${(error as BusinessError).message}`);
return;
}
if (result) {
console.info(`The nativeCallJs() return value is: ${result}`);
}
})
}
jsCallback(callee: string) {
let actionJson: JSONObject = JSONObject.parse(callee)
let action = actionJson.get("actionId");
switch (action) {
case 'getClientInfo':
this.getClientInfo();
break;
}
}
private getClientInfo() {
}
}
这里实现了业务侧的TESTJSExportObject,它是DIYJSExportObject的实现类
5.初始化webview组件
har包中我们的组件大概这样构建:
typescript
@Component
export struct DiyWebview {
@Consume('pathStack') pathStack: NavPathStack
controller: webview.WebviewController = new webview.WebviewController
diyJSExportObject: DIYJSExportObject | null = null;
build() {
NavDestination() {
Column() {
Web({ src: "", controller: this.controller })
.domStorageAccess(true)
.javaScriptAccess(true)
.zoomAccess(false)
.onControllerAttached(() => {
//注册代理
if (this.diyJSExportObject) { this.controller.registerJavaScriptProxy(this.diyJSExportObject.getInstance(this.pathStack,this.controller), "diyJSExportObject", ["jsCallback"])
}
// 进入时初始化
this.controller.loadUrl(this.url);
})
}
}
那么我们的调用,可以这样写:
typescript
import { DiyWebview } from '@library'
import { TESTJSExportObject } from './testJSExportObject'
DiyWebview({
diyJSExportObject: new TESTJSExportObject()
})
以上即完成了Webview组件和jsbridge的解耦,webview的基本功能在har包,而和webview的交互功能都在业务侧,这样即可方便webview获得端测的丰富的能力。
总结
构建产品矩阵的通用库是一件有意义且漫长的过程,鸿蒙之路才刚开始,期待未来百花齐放。本文中的代码大部分为精简源码,部分伪码,大家理解为主,自行实现,感谢阅读。