Hybrid开发模式在HarmonyOS NEXT里依然有一席之地。Web组件不是简单嵌个网页就完了——原生和Web的双向通信、JavaScript代理、导航控制、错误处理,每一块都有专门的API。回调参数类型跟Web标准不一样这点坑过不少人。
基础用法
Web组件的创建需要配合WebviewController:
import{webview}from'@kit.ArkWeb';@StatewebController:webview.WebviewController=newwebview.WebviewController();Web({src:'https://example.com',controller:this.webController}).width('100%').layoutWeight(1).javaScriptAccess(true)// 允许JS执行.domStorageAccess(true)// 允许DOM Storage.onlineImageAccess(true)// 允许加载网络图片.zoomAccess(true)// 允许缩放.overviewModeAccess(true)// 概览模式javaScriptAccess默认false,这点跟Android WebView不一样。不手动开启的话页面里的JS全部不执行,连onclick都不响应。
回调类型:最容易搞错的地方
Web组件的事件回调参数类型跟直觉不同,很多人第一次写都会用错类型:
// 错误写法.onPageBegin((event:string)=>{...}).onPageEnd((event:string)=>{...}).onErrorReceive((event:ErrorEvent)=>{...})// 正确写法.onPageBegin((event:OnPageBeginEvent)=>{this.addLog('开始加载: '+event.url);}).onPageEnd((event:OnPageEndEvent)=>{this.addLog('加载完成: '+event.url);this.canGoBack=this.webController.accessBackward();this.canGoForward=this.webController.accessForward();}).onErrorReceive((event:OnErrorReceiveEvent)=>{this.addLog('错误: '+event.error.getErrorInfo());})onPageBegin的参数是OnPageBeginEvent,有.url属性onPageEnd的参数是OnPageEndEvent,有.url属性onErrorReceive的参数是OnErrorReceiveEvent,不是ErrorEvent,通过.error.getErrorInfo()获取错误信息onSslErrorReceive的参数是SslErrorEvent
这些类型不需要额外import,直接用就行。但用错类型编译会报错,而且错误信息不一定能看出是类型名搞错了。
导航控制
WebviewController提供完整的导航能力:
// 前进后退判断letcanBack:boolean=this.webController.accessBackward();letcanForward:boolean=this.webController.accessForward();// 执行导航this.webController.backward();// 后退this.webController.forward();// 前进this.webController.refresh();// 刷新this.webController.clearHistory();// 清除历史// 加载URLthis.webController.loadUrl('https://example.com');// 加载本地文件this.webController.loadUrl($rawfile('index.html'));accessBackward/accessForward只判断不执行,backward/forward才真正跳转。在自定义导航栏里通常先check再action:
Button('←').enabled(this.canGoBack).onClick(()=>{this.webController.backward();})注意backward/forward是同步调用,但页面加载是异步的。点击后退后URL不会立即变,得等onPageEnd回调里再更新canGoBack/canGoForward。
JavaScript代理:原生→Web通信
javaScriptProxy是在Web页面里注入原生对象,让JS直接调用原生方法:
@StatewebController:webview.WebviewController=newwebview.WebviewController();aboutToAppear():void{this.webController.javaScriptProxy({object:this,name:'nativeBridge',methodList:['showToast','getData','navigateTo'],controller:this.webController});}// 被JS调用的方法必须是类的成员方法showToast(msg:string):void{// 显示原生Toast或弹窗this.addLog('JS调用showToast: '+msg);}getData():string{return'来自原生的数据';}Web页面里这样调用:
// JS代码nativeBridge.showToast('Hello from Web');letdata=nativeBridge.getData();methodList里声明的方法名必须和实际方法一一对应,未声明的方法JS调用不到。方法参数和返回值会自动做类型转换,但复杂对象建议用JSON字符串传递。
runJavaScript:Web→原生反向调用
从原生侧执行Web页面里的JS代码:
this.webController.runJavaScript('document.title',(result:string)=>{this.addLog('页面标题: '+result);});this.webController.runJavaScript('updateUI("new data")',(result:string)=>{this.addLog('JS执行结果: '+result);});runJavaScript是异步的,回调在JS执行完后触发。回调参数是JS表达式的返回值字符串。
这套机制构成了双向通信的闭环:JS通过javaScriptProxy调原生,原生通过runJavaScript调JS。
SSL错误处理
HTTPS页面遇到证书问题时触发onSslErrorReceive:
.onSslErrorReceive((event:SslErrorEvent)=>{// event.handler.confirm() // 忽略错误继续加载(不推荐生产环境用)// event.handler.cancel() // 取消加载(默认行为)this.addLog('SSL错误: '+event.error.toString());})默认行为是cancel,页面加载失败。confirm可以强行继续,但只适合调试。生产环境不该忽略SSL错误。
加载本地HTML
rawfile目录下的HTML文件可以直接加载:
Web({src:$rawfile('web/index.html'),controller:this.webController}).javaScriptAccess(true).domStorageAccess(true).allowFileAccess(true)// 允许文件访问.allowFileAccessFromFileURLs(true)// 允许file协议跨域本地HTML里引用其他资源用相对路径:
<scriptsrc="js/app.js"></script><linkrel="stylesheet"href="css/style.css"><imgsrc="img/logo.png">rawfile路径是resources/rawfile/web/,目录结构跟HTML里的引用路径对应。
自定义User-Agent
Web({src:url,controller:this.webController}).userAgent('MyApp/1.0 '+webview.getDefaultUserAgent())在默认UA后面追加自己的标识,方便服务端识别应用内WebView。
网络拦截
onOverrideUrlLoading可以拦截URL跳转:
.onOverrideUrlLoading((webResourceRequest:WebResourceRequest)=>{leturl:string=webResourceRequest.url;if(url.indexOf('myscheme://')===0){// 处理自定义schemethis.handleCustomScheme(url);returntrue;// 返回true阻止加载}returnfalse;// 返回false允许加载})返回true阻止当前URL加载,返回false放行。这个能力可以用来实现深度链接拦截。
onInterceptRequest拦截资源请求
更底层的拦截,可以替换网络请求的响应:
.onInterceptRequest((event:WebInterceptRequest)=>{if(event.url.indexOf('local://')===0){// 返回本地资源替换网络请求returnnewWebResourceResponse('text/html','UTF-8',$rawfile('cached/page.html'));}returnnull;// 返回null不拦截})这可以用来做离线缓存——有网时加载网络资源,断网时返回本地缓存。
调试
DevEco Studio支持Web组件调试。在Web组件上开启webDebuggingAccess:
Web({src:url,controller:this.webController}).webDebuggingAccess(true)然后Chrome浏览器打开chrome://inspect就能看到WebView页面,用DevTools调试JS。
注意这个开关只在debug模式生效,release包自动关闭。
性能优化
- 初始加载用
domStorageAccess(true)避免某些页面白屏 - 大量图片的页面用
onlineImageAccess控制 - 不需要缩放就关掉
zoomAccess - 预加载:提前创建WebviewController并loadUrl
- Web组件复用:搭配@Reusable避免反复创建
- 避免在onPageBegin里做耗时操作,会影响渲染
踩坑总结
| 问题 | 原因 | 解决 |
|---|---|---|
| JS不执行 | javaScriptAccess没开 | 设为true |
| DOM Storage报错 | domStorageAccess没开 | 设为true |
| 回调类型错误 | 用了string/ErrorEvent | 用OnPageBeginEvent等 |
| 本地文件404 | 路径不对或allowFileAccess没开 | 检查rawfile路径 |
| javaScriptProxy调不到 | methodList没声明 | 补充方法名 |
| 后退无反应 | accessBackward为false | 检查历史栈 |
| runJavaScript无回调 | JS表达式无返回值 | 检查JS语法 |
| HTTPS页面加载失败 | SSL证书问题 | onSslErrorReceive处理 |
WebView在HarmonyOS里不是"嵌个浏览器"那么简单,它是一个有完整生命周期和通信体系的第一等组件。原生和Web的边界通过javaScriptProxy+runJavaScript打通,回调类型用官方定义的Event类型,导航用WebviewController——搞清楚这三条线,复杂交互都能实现。