【HarmonyOS】ArkWeb——从入门到入土
1.使用场景
- 在应用内集成Web页面:应用可以在页面中使用Web组件,嵌入Web页面,从而降低开发成本
- 浏览器网页浏览场景:浏览器类应用可以使用Web组件来打开第三方网页
- 小程序:应用内嵌小程序功能的应用可以使用web组件来渲染小程序页面
2.ArkWeb进程
ArkWeb是多进程模型,分为应用进程、web渲染进程、webGpu渲染进程、web孵化进程和Foundation进程

- 应用进程中的web进程(应用唯一)
- 应用进程为主进程,包含UI主线程和Web的相关线程。包含网络线程、Video线程、Audio线程和IO线程
- Foundation进程(系统唯一)
- 负责接收应用进程要创建并启动一个新进程的请求,管理应用进程和web渲染进程的绑定关系
这里的创建并启动并非从0开始创建,而是由一定的基础
- 负责接收应用进程要创建并启动一个新进程的请求,管理应用进程和web渲染进程的绑定关系
- Web孵化进程(系统唯一)
- 负责接收Foundation进程的请求,执行孵化Web渲染进程与WebGpu进程
- 孵化完毕后对新的进程进行权限降级处理,并且预加载一些动态库,以提升运行速度
- Web渲染进程(应用可指定多Web实例之间共享或独立进程)
- 负责运行Web渲染进程引擎
- 负责运行ArkWeb执行引擎
- 提供接口供应用选择多web实例之间是否共享渲染进程
- WebGpu进程(应用唯一)
- 指挥GPU进行光栅化等底层硬件相关操作
3.Web组件的生命周期

- aboutToAppear:创建新自定义组件实例之后,在执行build前执行
- onControllerAttached:当Controller成功绑定时触发该回调
- onLoadIntercept:web组件加载url之前触发该回调,判断是否阻止此次访问
- onInterceptRequest:web组件加载url之间触发该回调,用于拦截url并返回一个自定义的响应数据
- onPageBegin:网页开始加载时触发该回调
- onProgressChange:告知开发者当前页面加载进度
- onPageEnd:网页加载完成触发该回调
4.Web组件的渲染和布局
web组件可以使用layoutMode(WebLayoutMode.FIT_CONTENT)属性实现组件高度跟随页面内容自适应变化
4.1异步渲染模式(默认)
在异步渲染模式下,web组件作为图形surface节点,独立送显。
建议在仅由web组件构成的页面中使用此模式,以提高性能
限制
- web组件的宽高不能超过7680px,超过会导致白屏
- 不支持动态切换模式

当前页面由web组件作为主体显示应用页面,web组件仅需占满手机屏幕大小即可,超出的H5页面部分ArkWeb会自动生成滚动条,便于滑动浏览
4.2同步渲染模式
同步渲染模式下,web组件作为图形canvas节点,web渲染跟随系统组件一起送显,可以渲染更长的web组件内容
建议在web组件与其他ArkUI组件共同滑动交互时使用
当前页面有web组件和ArkUI组件共同组成,此时H5界面与Web组件的高度需要一致,web内部不生成滚动条,
作为一个超长组件展示,通过Scroll组件实现应用内部的滚动,确保用户平滑浏览
5.应用中使用前端页面的JS
5.1应用侧调用前端页面函数
应用侧可以通过runJavaScript和runJavaScriptExt方法来调用前端页面的JS相关函数
参数类型有以下差异
- runJavaScript仅支持string类型
- runJavaScriptExt支持ArrayBuffer类型和string类型
//应用侧
.onClick(() => {// 调用前端页面无参函数。this.webviewController.runJavaScript('htmlTest()');
})/前端页面侧
<script>
//...// 无参函数。function htmlTest() {document.getElementById('text').style.color = 'yellow';}
//...
</script>
5.2前端页面调用应用侧函数
开发者使用web组件将应用侧代码注册到前端中,注册完成之后,前端页面
注册应用侧代码
- 在web组件初始化调用,使用javaScriptProxy接口
- 在web组价初始化完成后调用,使用registerJavaScriptProxy接口,这两种方式都需要和deleteJavaScriptRegister接口配合使用,防止内存泄漏
// Web组件加载本地index.html页面
Web({ src: $rawfile('index.html'), controller: this.webviewController})// 将对象注入到web端.javaScriptProxy({//定义要注入的JavaScript对象object: this.testObj,name: "testObjName",methodList: ["test"],controller: this.webviewController,// 可选参数asyncMethodList: [],//参与注册的应用侧js对象的异步方法permission: //json字符串,通过该字符串配置JSBridge的权限管控})
//前端页面使用
<script>function callArkTS() {let str = testObjName.test();document.getElementById("demo").innerHTML = str;console.info('ArkTS Hello World! :' + str);}
</script>
5.3建立应用侧与前端页面数据通道
前端页面和应用侧之间可以使用createWebMessagePorts接口创建消息端口来实现两端的通信
- 应用侧通过createWebMessagePorts接口创建两个消息端口,再把其中一个端口通过postMessage接口发送到前端页面,便可以子啊前端页面和应用侧之间互相发送消息
//应用侧代码.onClick(() => {try {// 1、创建两个消息端口。this.ports = this.controller.createWebMessagePorts();if (this.ports && this.ports[0] && this.ports[1]) {// 2、在应用侧的消息端口(如端口1)上注册回调事件。this.ports[1].onMessageEvent((result: webview.WebMessage) => {let msg = 'Got msg from HTML:';if (typeof (result) === 'string') {console.info(`received string message from html5, string is: ${result}`);msg = msg + result;} else if (typeof (result) === 'object') {if (result instanceof ArrayBuffer) {console.info(`received arraybuffer from html5, length is: ${result.byteLength}`);msg = msg + 'length is ' + result.byteLength;} else {console.info('not support');}} else {console.info('not support');}this.receivedFromHtml = msg;})// 3、将另一个消息端口(如端口0)发送到HTML侧,由HTML侧保存并使用。this.controller.postMessage('__init_port__', [this.ports[0]], '*');} else {console.error(`ports is null, Please initialize first`);}} catch (error) {console.error(`ErrorCode: ${(error as BusinessError).code}, Message: ${(error as BusinessError).message}`);}})
6.管理Web组件的网页加载
6.1加载页面
6.1.1加载网络页面
- 默认加载:在web组件的src字段中设置(不能通过状态变量改变地址)
- 修改地址:使用webController的loadUrl方法重新加载
6.1.2加载本地页面
- 默认加载:在web组件的src字段中使用$rawfile()绑定
- 修改地址同样使用loadUrl,不过参数需要传递$rawfile()
加载html格式的文本数据
当开发者不需要加兹安整个页面,只需要加载一些页面片段的时候,可使用Controller的loadData接口来实现快速加载
6.1.3加载html格式的文本数据
当开发者不需要加兹安整个页面,只需要加载一些页面片段的时候,可使用Controller的loadData接口来实现快速加载
Button('loadData').onClick(() => {try {// 点击按钮时,通过loadData,加载HTML格式的文本数据this.controller.loadData("<html><body bgcolor=\"white\">Source:<pre>source</pre></body></html>","text/html","UTF-8");} catch (error) {console.error(`ErrorCode: ${(error as BusinessError).code}, Message: ${(error as BusinessError).message}`);}})
同样也可以直接给src字段传递html格式的字符串数据
htmlStr: string = "data:text/html, <html><body bgcolor=\"white\">Source:<pre>source</pre></body></html>";build() {Column() {// 组件创建时,加载htmlStrWeb({ src: this.htmlStr, controller: this.controller })}}
6.1.4使用resource协议加载本地资源
Button('加载Resource资源').onClick(() => {try {// 通过resource加载resources/rawfile目录下的index1.html文件this.controller.loadUrl('resource://rawfile/index1.html');} catch (error) {console.error(`ErrorCode: ${error.code}, Message: ${error.message}`);}})
6.2加速Web页面的访问
6.2.1预解析和预连接
此方法通过prepareFoePageLoad接口来预解析或者预连接将要加载的页面
//在web组件的onAppear回调中对要加载的页面进行预连接
.onAppear(() => {// 指定第二个参数为true,代表要进行预连接,如果为false该接口只会对网址进行dns预解析// 第三个参数为要预连接socket的个数。最多允许6个。webview.WebviewController.prepareForPageLoad('https://www.example.com/', true, 2);})
该方式仅对url进行DNS解析以及建立tcp,但不会获取主资源子资源
也可以通过intiializeWebEngine接口来提前初始化内核,然后在初始化内核后调用
//在Ability.ets的onCreat中
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam) {console.log("EntryAbility onCreate");webview.WebviewController.initializeWebEngine();// 预连接时,需要将'https://www.example.com'替换成真实要访问的网站地址。webview.WebviewController.prepareForPageLoad("https://www.example.com/", true, 2);AppStorage.setOrCreate("abilityWant", want);console.log("EntryAbility onCreate done");}
6.2.2预加载
如果能够预测到web组件将要加载的页面或者即将要跳转的页面,就可以通过prefetchPage接口来预加载页面
预加载会提前下载页面所需的资源,包括主资源子资源,避免阻塞页面渲染。但不会执行前端网页的JS代码。
同时prefetchPage是webViewController的方法,需要一个已经关联好web组件的webViewController实例
//在onPageEnd的时候触发下一个要访问的页面的预加载
.onPageEnd(() => {// 预加载https://www.iana.org/help/example-domains。this.webviewController.prefetchPage('https://www.iana.org/help/example-domains');})
6.2.3预获取Post请求
此方法是针对请求级进行优化。通过prefetchResource接口预获取将要加载页面中的post请求。在页面加载结束时,可以通过clearPrefetchedResource接口清除不需要的预获取资源
//在web组件的onApper回调中,对要加载页面中的post请求进行预获取,在onPageEnd回调中清除缓存
.onAppear(() => {// 预获取时,需要将"https://www.example1.com/post?e=f&g=h"替换成真实要访问的网站地址。webview.WebviewController.prefetchResource({url: "https://www.example1.com/post?e=f&g=h",method: "POST",formData: "a=x&b=y",},[{headerKey: "c",headerValue: "z",},],"KeyX", 500);}).onPageEnd(() => {// 清除后续不再使用的预获取资源缓存。webview.WebviewController.clearPrefetchedResource(["KeyX",]);})
6.2.4预编译生成编译缓存
可以通过precompileJavaScript接口在页面加载前生成脚本文件的编译缓存
6.2.5静态资源免拦截缓存
可以通过injectOflineResource在页面加载前提前将图片,html、css文件等静态资源加入到应用的内存缓存中
6.3Web组件在不同的窗口间迁移
web组件能够实现在不同窗口的组件树上进行挂载或移除操作,例如将浏览器的Tab页拖出成独立窗口,或拖入浏览器的另一个窗口
基本原理是,通过BuilderNode创建web的离线节点,并结合自定义占位节点控制web节点的挂载与移除。当从一个组件树上移除并挂载到另一个组件树上时,就完成了web组件在窗口间的迁移
- 在common.ets中声明一个储存MyNodeController的Map,并且提供初始化函数,需要在初始化函数中完成BuilderNode的build方法,并加入Map
// 创建Map保存所需要的BuilderNode
let builderNodeMap : Map<string, BuilderNode<[Data]> | undefined> = new Map();
// 创建Map保存所需要的webview.WebviewController
let webControllerMap : Map<string, webview.WebviewController | undefined> = new Map();// 初始化需要UIContext对象,UIContext对象可通过窗口或自定义组件的getUIContext方法获取
export const createNWeb = (url: string, uiContext: UIContext) => {// 创建WebviewControllerlet webController = new webview.WebviewController() ;// 创建BuilderNodelet builderNode : BuilderNode<[Data]> = new BuilderNode(uiContext);// 创建动态Web组件builderNode.build(wrap, new Data(url, webController));// 保存BuilderNodebuilderNodeMap.set(url, builderNode);// 保存WebviewControllerwebControllerMap.set(url, webController);
}
// 自定义获取BuilderNode的接口
export const getBuilderNode = (url : string) : BuilderNode<[Data]> | undefined => {return builderNodeMap.get(url);
}
// 自定义获取WebviewController的接口
export const getWebviewController = (url : string) : webview.WebviewController | undefined => {return webControllerMap.get(url);
}//NodeController类
// 用于控制和反馈对应的NodeContainer上的节点的行为,需要与NodeContainer一起使用
export class MyNodeController extends NodeController {private builderNode: BuilderNode<[Data]> | null | undefined = null;private webController : webview.WebviewController | null | undefined = null;private rootNode : FrameNode | null = null;constructor(builderNode : BuilderNode<[Data]> | undefined, webController : webview.WebviewController | undefined) {super();this.builderNode = builderNode;this.webController = webController;}//....
}
- 在EntryAbility中调用初始化方法
createNWeb(defaultUrl, windowStage.getMainWindowSync().getUIContext());
- 在@Entry装饰的入口页面中调用获取函数获得Map中目标url对应的MyNodeController
private nodeController : MyNodeController =new MyNodeController(getBuilderNode(defaultUrl), getWebviewController(defaultUrl));
