HarmonyOS 6.0 WebView嵌入与交互

Hybrid开发模式在HarmonyOS NEXT里依然有一席之地。Web组件不是简单嵌个网页就完了------原生和Web的双向通信、JavaScript代理、导航控制、错误处理,每一块都有专门的API。回调参数类型跟Web标准不一样这点坑过不少人。

基础用法

Web组件的创建需要配合WebviewController:

typescript 复制代码
import { webview } from '@kit.ArkWeb';

@State webController: webview.WebviewController = new webview.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组件的事件回调参数类型跟直觉不同,很多人第一次写都会用错类型:

typescript 复制代码
// 错误写法
.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提供完整的导航能力:

typescript 复制代码
// 前进后退判断
let canBack: boolean = this.webController.accessBackward();
let canForward: boolean = this.webController.accessForward();

// 执行导航
this.webController.backward();    // 后退
this.webController.forward();     // 前进
this.webController.refresh();     // 刷新
this.webController.clearHistory(); // 清除历史

// 加载URL
this.webController.loadUrl('https://example.com');
// 加载本地文件
this.webController.loadUrl($rawfile('index.html'));

accessBackward/accessForward只判断不执行,backward/forward才真正跳转。在自定义导航栏里通常先check再action:

typescript 复制代码
Button('←')
  .enabled(this.canGoBack)
  .onClick(() => {
    this.webController.backward();
  })

注意backward/forward是同步调用,但页面加载是异步的。点击后退后URL不会立即变,得等onPageEnd回调里再更新canGoBack/canGoForward。

JavaScript代理:原生→Web通信

javaScriptProxy是在Web页面里注入原生对象,让JS直接调用原生方法:

typescript 复制代码
@State webController: webview.WebviewController = new webview.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页面里这样调用:

javascript 复制代码
// JS代码
nativeBridge.showToast('Hello from Web');
let data = nativeBridge.getData();

methodList里声明的方法名必须和实际方法一一对应,未声明的方法JS调用不到。方法参数和返回值会自动做类型转换,但复杂对象建议用JSON字符串传递。

runJavaScript:Web→原生反向调用

从原生侧执行Web页面里的JS代码:

typescript 复制代码
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:

typescript 复制代码
.onSslErrorReceive((event: SslErrorEvent) => {
  // event.handler.confirm()  // 忽略错误继续加载(不推荐生产环境用)
  // event.handler.cancel()   // 取消加载(默认行为)
  this.addLog('SSL错误: ' + event.error.toString());
})

默认行为是cancel,页面加载失败。confirm可以强行继续,但只适合调试。生产环境不该忽略SSL错误。

加载本地HTML

rawfile目录下的HTML文件可以直接加载:

typescript 复制代码
Web({ src: $rawfile('web/index.html'), controller: this.webController })
  .javaScriptAccess(true)
  .domStorageAccess(true)
  .allowFileAccess(true)       // 允许文件访问
  .allowFileAccessFromFileURLs(true)  // 允许file协议跨域

本地HTML里引用其他资源用相对路径:

html 复制代码
<script src="js/app.js"></script>
<link rel="stylesheet" href="css/style.css">
<img src="img/logo.png">

rawfile路径是resources/rawfile/web/,目录结构跟HTML里的引用路径对应。

自定义User-Agent

typescript 复制代码
Web({ src: url, controller: this.webController })
  .userAgent('MyApp/1.0 ' + webview.getDefaultUserAgent())

在默认UA后面追加自己的标识,方便服务端识别应用内WebView。

网络拦截

onOverrideUrlLoading可以拦截URL跳转:

typescript 复制代码
.onOverrideUrlLoading((webResourceRequest: WebResourceRequest) => {
  let url: string = webResourceRequest.url;
  if (url.indexOf('myscheme://') === 0) {
    // 处理自定义scheme
    this.handleCustomScheme(url);
    return true;  // 返回true阻止加载
  }
  return false;    // 返回false允许加载
})

返回true阻止当前URL加载,返回false放行。这个能力可以用来实现深度链接拦截。

onInterceptRequest拦截资源请求

更底层的拦截,可以替换网络请求的响应:

typescript 复制代码
.onInterceptRequest((event: WebInterceptRequest) => {
  if (event.url.indexOf('local://') === 0) {
    // 返回本地资源替换网络请求
    return new WebResourceResponse(
      'text/html', 'UTF-8',
      $rawfile('cached/page.html')
    );
  }
  return null;  // 返回null不拦截
})

这可以用来做离线缓存------有网时加载网络资源,断网时返回本地缓存。

调试

DevEco Studio支持Web组件调试。在Web组件上开启webDebuggingAccess:

typescript 复制代码
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------搞清楚这三条线,复杂交互都能实现。

相关推荐
b130538100491 小时前
鸿蒙应用开发实战【41】— 登录页面LoginPage完整开发
服务器·华为·harmonyos·鸿蒙系统
2501_918582371 小时前
鸿蒙应用开发实战【31】— CardDao 卡号增删改查完整实现
华为·harmonyos·鸿蒙系统
全堆鸿蒙1 小时前
鸿蒙应用开发实战【36】— 数据查询优化:predicates 链式调用
数据库·harmonyos·鸿蒙系统
特立独行的猫a1 小时前
HarmonyOS鸿蒙PC三方库移植:Node.js 三方库鸿蒙 PC 移植指南
华为·node.js·node·harmonyos·三方库移植·鸿蒙pc
心中有国也有家1 小时前
AtomGit Flutter 鸿蒙客户端:错误处理与优雅降级策略
android·javascript·flutter·华为·harmonyos
youtootech10 小时前
HarmonyOS《柚兔学伴》项目实战09-待办列表 UI——List+ForEach+滑动删除
数据结构·ui·华为·list·harmonyos
不羁的木木10 小时前
HarmonyOS APP实战-基于Image Kit的图像处理APP - 第6篇:图片滤镜效果实现
图像处理·深度学习·harmonyos
拥抱太阳061611 小时前
HarmonyOS 应用开发《掌上英语》第9篇—ArkTS @ObservedV2 + @Trace 响应式编程精讲
华为·harmonyos
拥抱太阳061611 小时前
HarmonyOS 应用开发《掌上英语》第10篇——@Local vs @Param vs @Trace 组件状态管理三剑客
华为·harmonyos