文章目录
-
- 每日一句正能量
- 摘要
- 一、Web 组件核心能力解析
-
- 1.1 组件定位与内核架构
- 1.2 三种加载方式对比
- 1.3 控制器核心 API
- 二、生命周期与事件回调体系
-
- 2.1 完整事件链路
- 2.2 生命周期最佳实践
- 三、JSBridge 双向通信机制
-
- 3.1 通信架构总览
- 3.2 方式一:runJavaScript(原生 → H5)
- 3.3 方式二:registerJavaScriptProxy(H5 → 原生)
- 3.4 方式三:onPrompt 通道(H5 → 原生)
- 四、SmartWebView 封装组件实现
-
- 4.1 设计目标
- 4.2 核心代码实现
- 4.3 页面调用示例
- 五、安全策略与性能优化
-
- 5.1 域名白名单机制
- 5.2 敏感权限管控
- 5.3 缓存与预加载策略
- 5.4 内存管理
- 六、总结

每日一句正能量
生活的刁难,并不是要你变得气急败坏,而是要你变得更加从容。 生活不是故意为难你,但困境确实在逼迫你升级。如果你每一次被刁难后只是更暴躁、更紧绷,那你只是被事情碾压了;如果你慢慢学会了稳住、放下、转身,那困境就完成了它的“教练”功能。
摘要
摘要:在 Hybrid 应用开发中,Web 组件(WebView)是连接原生能力与 Web 生态的关键桥梁。HarmonyOS 6(API 23)基于 Chromium 内核提供了功能完备的 Web 组件,支持网络页面加载、本地资源渲染、HTML 片段注入以及与 JavaScript 的双向通信。本文将从加载机制、生命周期管理、JSBridge 通信、安全策略四个维度展开,封装一套生产级 SmartWebView 组件,涵盖加载状态可视化、错误降级、白名单控制、原生与 H5 互调等核心能力,帮助开发者快速构建稳定可靠的混合应用页面。
一、Web 组件核心能力解析
1.1 组件定位与内核架构
Web 组件是 ArkUI 框架提供的内嵌浏览器容器,基于 Chromium 内核构建,完整支持 HTML5、CSS3 与 ECMAScript 标准。与系统浏览器相比,Web 组件具备以下独特优势:
- 深度集成:可直接嵌入 ArkTS 页面布局,与原生组件无缝混排;
- 双向通信:通过 WebviewController 实现原生与 JavaScript 的函数互调;
- 生命周期可控:提供 onPageStart、onPageEnd、onProgressChange 等完整事件链;
- 资源隔离:每个 Web 组件拥有独立的 Cookie、LocalStorage 与缓存空间。
1.2 三种加载方式对比
HarmonyOS Web 组件支持三种内容加载模式,适用于不同业务场景: 
Web 组件三种加载方式对比
| 网络页面 | src: 'https://…' | H5 活动页、在线文档、第三方页面 | 必须申请 ohos.permission.INTERNET 权限 |
| 本地页面 | src: $rawfile("index.html") | 离线帮助文档、内置规则页、隐私协议 | 文件存放于 resources/rawfile 目录 |
| HTML 片段 | controller.loadData() | 富文本展示、动态渲染、邮件内容 | 需指定 MIME 类型与编码格式 |
权限声明示例(module.json5):
{
"module": {
"requestPermissions": [
{ "name": "ohos.permission.INTERNET" }
]
}
}
1.3 控制器核心 API
WebviewController 是 Web 组件的"遥控器",提供对网页行为的精确控制:
import { webview } from '@kit.ArkWeb';
// 创建控制器
controller: webview.WebviewController = new webview.WebviewController();
// 页面导航
this.controller.loadUrl('https://example.com'); // 加载指定 URL
this.controller.back(); // 后退
this.controller.forward(); // 前进
this.controller.refresh(); // 刷新当前页
this.controller.accessStep(–2); // 回退 2 步
// 页面信息
const title = this.controller.getTitle(); // 获取页面标题
const url = this.controller.getUrl(); // 获取当前 URL
二、生命周期与事件回调体系
2.1 完整事件链路
Web 组件提供了覆盖页面加载全周期的回调事件,开发者可据此实现加载状态可视化与异常处理: 
Web 组件生命周期与关键事件回调
| onPageStart | 页面开始加载 | 显示 Loading 进度条、重置状态 |
| onProgressChange | 加载进度变化(0~100) | 更新进度百分比、骨架屏过渡 |
| onPageEnd | 页面加载完成 | 隐藏 Loading、埋点上报 |
| onErrorReceive | 页面加载出错(404/超时等) | 错误降级、显示重试页面 |
| onTitleReceive | 页面标题变化 | 动态更新原生导航栏标题 |
| onScaleChange | 页面缩放比例变化 | 自适应适配、缩放限制 |
| onScroll | 页面滚动事件 | 联动原生组件、悬浮按钮显隐 |
2.2 生命周期最佳实践
Web({ src: this.webUrl, controller: this.controller })
.width('100%')
.height('100%')
.onPageStart((event) => {
this.isLoading = true;
this.loadProgress = 0;
this.errorInfo = '';
console.info(`页面开始加载: ${event.url}`);
})
.onProgressChange((event) => {
this.loadProgress = event.newProgress;
// 进度超过 80% 时渐隐 Loading
if (event.newProgress > 80) {
this.isLoading = false;
}
})
.onPageEnd((event) => {
this.isLoading = false;
this.loadProgress = 100;
// 页面加载完成埋点
hilog.info(0x0000, 'WebPage', '页面加载完成: %{public}s', event.url);
})
.onErrorReceive((event) => {
this.isLoading = false;
this.hasError = true;
this.errorInfo = `错误码: ${event.error.getErrorCode()}, 描述: ${event.error.getErrorInfo()}`;
console.error(`页面加载异常: ${this.errorInfo}`);
})
三、JSBridge 双向通信机制
3.1 通信架构总览
Web 组件与原生 ArkTS 之间的通信是 Hybrid 应用的核心能力。HarmonyOS 提供了多种通信通道,开发者应根据场景选择最合适的方案: 
Web 组件与 JavaScript 双向通信架构
3.2 方式一:runJavaScript(原生 → H5)
runJavaScript 允许原生代码在 Web 页面上下文中执行任意 JavaScript 代码,适用于向 H5 推送数据或触发页面行为:
// 原生调用 H5 的 JS 函数
async notifyH5(data: string): Promise<void> {
const script = `window.receiveNativeData && window.receiveNativeData('${data}')`;
try {
const result = await this.controller.runJavaScript(script);
console.info('JS 执行结果:', result);
} catch (error) {
console.error('JS 执行失败:', error);
}
}
3.3 方式二:registerJavaScriptProxy(H5 → 原生)
通过 registerJavaScriptProxy 可将原生对象及其方法注入到 H5 的 window 对象中,H5 可直接调用原生能力:
// 定义可被 H5 调用的原生接口
class NativeBridge {
// H5 调用此方法获取设备信息
getDeviceInfo(): string {
return JSON.stringify({
platform: 'HarmonyOS',
version: '6.0',
model: deviceInfo.model
});
}
// H5 调用此方法关闭当前页面
closePage(): void {
router.back();
}
// H5 调用此方法分享内容
shareContent(title: string, url: string): void {
// 调用系统分享能力
// …
}
}
// 注册代理(需在 onPageStart 或 aboutToAppear 中调用)
this.controller.registerJavaScriptProxy(
new NativeBridge(), // 原生对象实例
'nativeBridge', // H5 中暴露的对象名
['getDeviceInfo', 'closePage', 'shareContent'] // 允许调用的方法白名单
);
H5 侧调用示例:
<script>
// 直接调用原生暴露的方法
const deviceInfo = nativeBridge.getDeviceInfo();
console.log('设备信息:', JSON.parse(deviceInfo));
document.getElementById('btn-share').onclick = () => {
nativeBridge.shareContent('文章标题', 'https://example.com');
};
</script>
3.4 方式三:onPrompt 通道(H5 → 原生)
onPrompt 是最轻量的通信方式,H5 通过 window.prompt(action, data) 向原生发送消息,原生在 onPrompt 回调中解析并响应:
Web({ src: this.webUrl, controller: this.controller })
.onPrompt((event) => {
const action = event.message;
const data = event.value;
switch (action) {
case 'getToken':
// 返回用户 Token 给 H5
event.result.setResult(this.userToken);
break;
case 'openCamera':
// 触发原生相机
this.openCamera();
event.result.setResult('camera_opened');
break;
case 'navigateTo':
// 路由跳转
router.pushUrl({ url: data });
event.result.setResult('navigated');
break;
default:
event.result.setResult('unknown_action');
}
return true;
})
H5 侧调用:
// 获取原生 Token
const token = prompt('getToken', '');
// 打开相机
prompt('openCamera', '');
// 页面跳转
prompt('navigateTo', 'pages/DetailPage');
四、SmartWebView 封装组件实现
4.1 设计目标
生产环境中的 Web 页面需要处理大量边界场景:网络波动、白屏超时、域名安全、返回键拦截等。本文封装的 SmartWebView 组件旨在解决以下痛点:
- 加载可视化:骨架屏 + 进度条 + 加载动画三重保障;
- 错误降级:网络异常时显示自定义错误页,支持一键重试;
- 白名单安全:仅允许加载指定域名,防止恶意跳转;
- 返回键适配:智能判断网页能否后退,避免直接退出页面;
- 标题同步:自动将 H5 页面标题同步到原生导航栏。

SmartWebView 封装组件架构图
4.2 核心代码实现
// SmartWebView.ets
import { webview } from '@kit.ArkWeb';
import { router } from '@kit.ArkUI';
interface SmartWebOptions {
url: string; // 初始加载地址
showProgress?: boolean; // 是否显示进度条
showSkeleton?: boolean; // 是否显示骨架屏
allowDomains?: string[]; // 允许的域名白名单
onTitleChange?: (title: string) => void;
onLoadError?: (code: number, info: string) => void;
}
@Component
export struct SmartWebView {
@Prop url: string;
@Prop showProgress: boolean = true;
@Prop showSkeleton: boolean = true;
@Prop allowDomains: string[] = [];
private onTitleChange?: (title: string) => void;
private onLoadError?: (code: number, info: string) => void;
@State private controller: webview.WebviewController = new webview.WebviewController();
@State private isLoading: boolean = true;
@State private loadProgress: number = 0;
@State private hasError: boolean = false;
@State private errorCode: number = 0;
@State private errorInfo: string = '';
@State private pageTitle: string = '';
aboutToAppear(): void {
// 域名安全检查
if (this.allowDomains.length > 0 && !this.isDomainAllowed(this.url)) {
this.hasError = true;
this.errorInfo = '当前域名不在白名单中';
return;
}
}
build() {
Stack({ alignContent: Alignment.TopStart }) {
// Web 内容区
Web({ src: this.url, controller: this.controller })
.width('100%')
.height('100%')
.domStorageAccess(true) // 开启 DOM Storage
.imageAccess(true) // 允许加载图片
.onlineImageAccess(true) // 允许加载网络图片
.javaScriptAccess(true) // 允许执行 JS
.zoomAccess(false) // 禁止手势缩放(由原生控制)
.width('100%')
.height('100%')
.onPageStart(() => {
this.isLoading = true;
this.loadProgress = 0;
this.hasError = false;
})
.onProgressChange((event) => {
this.loadProgress = event.newProgress;
})
.onPageEnd((event) => {
this.isLoading = false;
this.loadProgress = 100;
})
.onTitleReceive((event) => {
this.pageTitle = event.title;
this.onTitleChange?.(event.title);
})
.onErrorReceive((event) => {
this.isLoading = false;
this.hasError = true;
this.errorCode = event.error.getErrorCode();
this.errorInfo = event.error.getErrorInfo();
this.onLoadError?.(this.errorCode, this.errorInfo);
})
.onCommonDialog((event) => {
// 拦截 alert / confirm / prompt
if (event.dialogResult) {
event.dialogResult.handleConfirm();
}
return true;
})
// 骨架屏(加载初期显示)
if (this.showSkeleton && this.isLoading && this.loadProgress < 30) {
Column({ space: 12 }) {
ForEach([1, 2, 3, 4, 5], () => {
Row() {
Row()
.width(60).height(60)
.backgroundColor('#f0f0f0')
.borderRadius(8)
Column({ space: 8 }) {
Row().width('70%').height(16).backgroundColor('#f0f0f0').borderRadius(4)
Row().width('50%').height(14).backgroundColor('#f0f0f0').borderRadius(4)
}.layoutWeight(1).alignItems(HorizontalAlign.Start)
}
.width('100%')
.padding(16)
})
}
.width('100%')
.height('100%')
.backgroundColor('#ffffff')
}
// 顶部进度条
if (this.showProgress && this.isLoading && this.loadProgress < 100) {
Row()
.width(`${this.loadProgress}%`)
.height(3)
.backgroundColor('#007DFF')
.transition(TransitionEffect.OPACITY)
}
// 错误降级页
if (this.hasError) {
Column({ space: 16 }) {
Image($r('app.media.ic_error_network'))
.width(120).height(120)
Text('页面加载失败')
.fontSize(18).fontColor('#333333').fontWeight(FontWeight.Bold)
Text(this.errorInfo)
.fontSize(14).fontColor('#999999')
.maxLines(2).textOverflow({ overflow: TextOverflow.Ellipsis })
Button('重新加载')
.width(140).height(40)
.backgroundColor('#007DFF')
.fontColor('#FFFFFF')
.onClick(() => {
this.hasError = false;
this.controller.refresh();
})
}
.width('100%')
.height('100%')
.backgroundColor('#ffffff')
.justifyContent(FlexAlign.Center)
}
}
.width('100%')
.height('100%')
}
// 域名白名单校验
private isDomainAllowed(url: string): boolean {
try {
const domain = new URL(url).hostname;
return this.allowDomains.some(allowed => domain.includes(allowed));
} catch {
return false;
}
}
// 返回键拦截:优先网页后退
public handleBackPress(): boolean {
if (this.controller.accessBackward()) {
this.controller.backward();
return true; // 拦截返回键
}
return false; // 允许退出页面
}
}
4.3 页面调用示例
// ArticlePage.ets
import { SmartWebView } from '../components/SmartWebView';
@Entry
@Component
struct ArticlePage {
@State articleUrl: string = 'https://developer.huawei.com/consumer/cn/doc/harmonyos-guides';
@State navTitle: string = '加载中…';
// 获取 SmartWebView 实例引用
private webRef: SmartWebView | null = null;
build() {
Column() {
// 原生导航栏
Row() {
Image($r('app.media.ic_back'))
.width(24).height(24)
.onClick(() => {
// 优先网页后退
if (!this.webRef?.handleBackPress()) {
router.back();
}
})
Text(this.navTitle)
.fontSize(18)
.fontWeight(FontWeight.Bold)
.fontColor('#1a1a1a')
.layoutWeight(1)
.textAlign(TextAlign.Center)
Blank().width(40)
}
.width('100%')
.height(56)
.padding({ left: 16, right: 16 })
.backgroundColor('#ffffff')
.shadow({ radius: 2, color: 'rgba(0,0,0,0.05)', offsetY: 2 })
// Web 内容区
SmartWebView({
url: this.articleUrl,
showProgress: true,
showSkeleton: true,
allowDomains: ['developer.huawei.com', 'harmonyos.com'],
onTitleChange: (title: string) => {
this.navTitle = title;
},
onLoadError: (code: number, info: string) => {
promptAction.showToast({ message: `加载异常: ${code}` });
}
})
.width('100%')
.layoutWeight(1)
}
.width('100%')
.height('100%')
.backgroundColor('#f5f5f5')
}
}
五、安全策略与性能优化
5.1 域名白名单机制
生产环境中,Web 组件可能被恶意利用进行钓鱼跳转。allowDomains 白名单机制可有效防范此类风险:
// 严格白名单校验
private isDomainAllowed(url: string): boolean {
const allowed = ['developer.huawei.com', 'docs.example.com'];
const domain = new URL(url).hostname;
// 精确匹配或子域名匹配
return allowed.some(d => domain === d || domain.endsWith('.' + d));
}
5.2 敏感权限管控
当 H5 页面需要调用相机、定位、麦克风等敏感能力时,需在 module.json5 中声明对应权限,并通过 onPermissionRequest 回调进行运行时授权:
Web({ src: this.url, controller: this.controller })
.onPermissionRequest((event) => {
AlertDialog.show({
title: '权限申请',
message: `网页请求 ${event.request.getOrigin()} 的 ${event.request.getAccessibleResource()} 权限`,
primaryButton: {
value: '允许',
action: () => event.request.grant(event.request.getAccessibleResource())
},
secondaryButton: {
value: '拒绝',
action: () => event.request.deny()
}
});
})
5.3 缓存与预加载策略
// 开启缓存
Web({ src: this.url, controller: this.controller })
.cacheMode(CacheMode.Default) // 默认缓存模式
// 预加载(在 aboutToAppear 中提前初始化 Controller)
aboutToAppear(): void {
// 预创建 Controller 可缩短首屏时间约 200~400ms
this.controller = new webview.WebviewController();
}
5.4 内存管理
Web 组件占用内存较大,页面销毁时务必释放资源:
aboutToDisappear(): void {
// 停止所有加载任务
this.controller.stop();
// 清理缓存(按需调用)
this.controller.removeCache(true);
}
六、总结
本文从 Web 组件的加载机制出发,系统梳理了网络页面、本地资源、HTML 片段三种加载方式,深入解析了覆盖页面全生命周期的回调事件链,并基于 runJavaScript、registerJavaScriptProxy、onPrompt 三种通道实现了原生与 H5 的双向通信。最终封装的生产级 SmartWebView 组件具备以下核心特性:
- 加载可视化:骨架屏 + 进度条 + 加载动画,消除白屏焦虑;
- 错误降级:网络异常时自动切换至错误页,支持一键重试;
- 安全可控:域名白名单 + 敏感权限拦截,防止恶意利用;
- 体验一致:标题同步、返回键智能拦截、缩放控制等细节打磨。
在 Hybrid 应用日益普及的今天,掌握 Web 组件的深度封装能力,是 HarmonyOS 开发者打通原生与 Web 生态的必备技能。
转载自:https://blog.csdn.net/u014727709/article/details/163370686 欢迎 👍点赞✍评论⭐收藏,欢迎指正


