欢迎光临
我们一直在努力

基于 Web 组件的网页加载体系与 JSBridge 双向通信深度封装

文章目录

    • 每日一句正能量
    • 摘要
    • 一、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 组件三种加载方式对比

加载方式API 形式适用场景注意事项
网络页面 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 欢迎 👍点赞✍评论⭐收藏,欢迎指正

赞(0)
未经允许不得转载:171主机测评 » 基于 Web 组件的网页加载体系与 JSBridge 双向通信深度封装
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址