HarmonyOS Web组件(二)
1. HarmonyOS Web组件
官方文档
1.1. 混合开发的背景和好处
混合开发(Hybrid Development)是一种结合原生应用和Web应用的开发模式,旨在同时利用两者的优势。随着移动应用需求的多样化和复杂化,单一的开发方式往往难以满足所有需求。混合开发提供了一种灵活、高效的解决方案,特别是在以下方面具有显著的优势:
(1)跨平台兼容:混合开发允许开发者编写一次代码,并在多个平台(如Android、iOS、HarmonyOS等)上运行。这大大减少了开发和维护成本。
(2)快速迭代:Web技术(如HTML、CSS、JavaScript)的快速开发和部署能力,使得混合应用可以更快地进行迭代和更新。
(3)丰富的Web生态:借助丰富的Web生态系统,开发者可以利用大量现有的Web库和框架,快速实现复杂功能。
(4)原生性能:通过将关键功能部分使用原生代码实现,混合应用可以在保证性能的同时,享受Web开发的灵活性。
1.2.混合开发应用的结构
为了更好地理解混合开发的概念,以下是一张示意图,展示了混合开发架构中原生代码与Web代码的结合。
(图中展示了混合开发应用的结构,其中包括:
(1)原生层 Native Layer:包括操作系统(如HarmonyOS)、设备硬件和原生API,提供高性能和底层功能支持。
(2)Web层 Web Layer:包括HTML、CSS、JavaScript等Web技术,负责应用的界面和逻辑部分。
(3)桥接层 Bridge Layer:连接原生层和Web层,允许两者之间的数据和功能交互。
通过这种架构,开发者可以在Web层快速构建界面和业务逻辑,同时利用原生层提供的高性能和丰富功能,实现混合开发的最佳效果。
在HarmonyOS NEXT Developer Beta1版本中,ArkTS 提供了强大的混合开发能力,允许开发者在应用中嵌入 Web 组件,利用 Web 技术构建应用的一部分。接下来,我将介绍如何在 ArkTS 中使用 Web 组件。
1.3. ArkTS Web 组件教程
1.3.1. 语法说明
在 ArkTS 中,混合开发主要通过 Web 组件和 WebView API 来实现。通过这些工具,可以加载和显示网页内容,进行页面控制和数据交互。
官方文档
1.3.1.1. 基本语法
Web 组件用于在界面中嵌入一个网页浏览器。它的常用属性包括:
src:指定要加载的网页资源地址。
controller:webview控制器。
src: ResourceStr = 'https://m.jd.com' Web({ src: this.src, controller: this.controller })
注意:访问在线网页时需添加网络权限:ohos.permission.INTERNET
1.3.1.2 . 事件处理
Web 组件支持多种事件,如:
(1)onPageBegin:页面开始加载时触发。
(2)onPageEnd:页面加载完成时触发。
(3)onErrorReceive:页面加载出错时触发。
(4)onTitleReceive:网页document标题更改时触发该回调。
(5)onProgressChange:网页加载进度变化时触发该回调。
(6)onRefreshAccessedHistory:加载网页页面完成时触发该回调,用于应用更新其访问的历史链接。
(7)onAppear:组件挂载显示后触发此回调(通用事件)。
Web({ src: this.src, controller: this.controller }) .onProgressChange((data) => { }) .onPageBegin(() => { }) .onPageEnd(() => { }) .onErrorReceive((e) => { }) .onTitleReceive((data) => { }) .onRefreshAccessedHistory(() => { }) .onAppear(() => { })
1.3.1.3 . WebView API
WebView API 提供了一组方法和属性,用于更细粒度地控制 Web 组件,如加载URL、执行JavaScript代码等。
import { webview } from '@kit.ArkWeb' // 1. 创建 WebView 实例 controller = new webview.WebviewController() // 2. 调用WebView 实例 api 进行控制 run() { // 动态加载页面 this.controller.loadUrl('url') // 注入 js 对象 this.controller.registerJavaScriptProxy // ... } Web({ src: this.src, controller: this.controller })
1.3. 实战案例
1.3.1. 新建工程
主要目录结构:
|-- entry | |-- src | | |-- main | | | |-- ets | | | | |-- pages | | | | | |-- Index.ets(案例代码) |-- module.json5
1.3.2. 配置权限
在 module.json5 中配置网络权限:
{ "module": { "name": "entry", "type": "entry", "description": "$string:module_desc", // ... "requestPermissions": [ { "name": "ohos.permission.INTERNET" } ], "deliveryWithInstall": true, "installationFree": false, "pages": "$profile:main_pages", "abilities": [ // ... ], "extensionAbilities": [ // ... ] } }
1.3.3. 加载页面
在 Index.ets 文件中,使用 Web 组件和 WebView API,加载web页面:
@Entry @Component struct WebPage { // 1. web 组件基本使用 src: ResourceStr = 'https://m.jd.com' // webview控制器 controller = new webview.WebviewController() build() { Navigation() { Web({ src: this.src, controller: this.controller }) } .title('混合开发') .titleMode(NavigationTitleMode.Mini) } }
1.3.4. 页面标题
获取当前加载网页标题,进行动态展示
// ... @State // 当前网页的标题 title: string = '' build() { Navigation() { Web({ src: this.src, controller: this.controller }) .onTitleReceive((data) => { this.title = data?.title || '--' }) } .title(this.title) .titleMode(NavigationTitleMode.Mini) }
1.3.5. 页面进度
获取页面进度数据,添加进度条效果:
(1)第一步:定义状态,存储加载中和进度数据
// 当前访问页面历史记录索引 historyCurrIndex: number = 0
(2)第二步:添加事件获取历史记录
// ... .onRefreshAccessedHistory(() => { // == 网页加载完成可访问页面历史记录 == // 获取当前Webview的页面历史记录列表 const history = this.controller.getBackForwardEntries() // 当前页面历史记录索引 this.historyCurrIndex = history.currentIndex // 历史记录索引的数量,最多保存50条,超过时起始记录会被覆盖 console.log('mgx', history.size) })
(3)第三步:在原生返回钩子函数中控制逻辑
onBackPress() { // 在web容器中, 当前页面之前还有页面, 则容器内返回上一页 if (this.historyCurrIndex > 0) { this.controller.backward() } else { // 返回原生页面 router.back() } // 自定义返回逻辑 return true }
1.3.6. 在页面标题中添加菜单图标,点击刷新页面。
(1)第一步:给Navigation组件右侧添加菜单(图标随便)
build() { Navigation() { // ... } .title(this.title) .titleMode(NavigationTitleMode.Mini) .menus(this.titleMenus) }
(2)第二步:定义菜单 Builder 绑定事件控制刷新
@Builder titleMenus() { Row() { Image($r('app.media.startIcon')) .width(18) .aspectRatio(1) .margin({ right: 10 }) .onClick(() => { // 刷新网页 this.controller.refresh() }) } .width(50) .height('100%') .justifyContent(FlexAlign.End) .alignItems(VerticalAlign.Center) }
1.3.7. JSBridge代理
接下来,进入重点环节,我们来学习实操下如何向 Web 容器的网页中注入 JS 对象,给网页提供原生的能力支持,例如: 选择相册、拍照、传感器等底层能力。
核心依赖webview 提供的registerJavaScriptProxy api,提供了应用与Web组件加载的网页之间强大的交互能力。注入JavaScript对象到window对象中,并在window对象中调用该对象的方法。
官方参考文档
(1)第一步:定义注入 JS 的类型
示例注入了两个函数:
test 方法,获取网页调用后传参
select 方法,选择原生相册,获取选择图片结果显示到网页中
interface InjectJs { // 测试方法 test: (a: string) => void // 选择相册 select: () => Promise } type InjectKeys = keyof InjectJs
(2)第二步:定义注入的方法和逻辑
// 2. JSBridge代理 webInject() { this.controller.registerJavaScriptProxy({ // 参数 1:注入应用侧JavaScript对象 // 参数 2:注入对象的名称,与window中调用的对象名一致 // 参数 3:注入后window对象可以通过此名字访问应用侧JavaScript对象 test: (a) => { AlertDialog.show({ message: `网页传参:${a}` }) }, select: async () => { // 1. 打开相册选择图片 const photoSelectOptions = new picker.PhotoSelectOptions() photoSelectOptions.MIMEType = picker.PhotoViewMIMETypes.IMAGE_TYPE; photoSelectOptions.maxSelectNumber = 1; const photoPicker = new picker.PhotoViewPicker(); const res = await photoPicker.select(photoSelectOptions) // 2. 文件操作 // 2.1 获取照片的uri地址 const uri = res.photoUris[0] // 2.2 根据uri同步打开文件 const file = fs.openSync(uri) // 2.3 同步获取文件的详细信息 const stat = fs.statSync(file.fd) // 2.4 创建缓冲区存储读取的文件流 const buffer = new ArrayBuffer(stat.size) // 2.5 开始同步读取文件流到缓冲区 fs.readSync(file.fd, buffer) // 2.6 关闭文件流 fs.closeSync(file) // 3. 转成base64编码的字符串 const helper = new util.Base64Helper() const str = helper.encodeToStringSync(new Uint8Array(buffer)) return 'data:image/png;base64,' + str } } as InjectJs, 'mg', [ 'test', 'select', ] as InjectKeys[]) }
(3)第三步:在通用事件onAppear 执行注入
Web({ src: this.src, controller: this.controller }) // ... .onAppear(() => { // JSBridge代理注入 this.webInject() })
(4)第四步:本地创建网页加载
这里使用的是原生 js 开发网页端,实际开发中可选择 vue 、 react 、 uni-app 等框架开发,效率更高。
目录:entry/src/main/resources/rawfile/index.html
混合开发 * { margin: 0; padding: 0; } ul li { padding: 10px; }window.onload = () => { document.querySelector('#test').addEventListener('click', () => { mg.test('我是来自web数据啦') }) document.querySelector('#sel').addEventListener('click', async () => { const b64 = await mg.select() document.querySelector('#img').setAttribute('src', b64) }) }H5页面
- 调用原生test
- 调用原生select选择相册
1.3.8. 执行网页JS
有时候,我们需要在原生端调用网页中 JS,完成一些业务需求。
(1)第一步:网页中挂载 JS到 window
重新运行项目,点击按钮调用注入的函数测试
window.go = () => { return '我是网页JS函数' } window.onload = () => { // ... }
(2)原生端调用挂载的 JS
借助 webview 提供runJavaScript api 异步执行JavaScript脚本,并通过回调方式返回脚本执行的结果。
官方参考文档
// 3. 执行网页 js runJs() { // this.controller.loadUrl($rawfile('index.html')) this.controller.runJavaScript('go()', (error, res) => { if (error) { return AlertDialog.show({ message: error.message }) } // 获取执行返回值 AlertDialog.show({ message: res }) }) } build() { Navigation() { Button('执行网页 js') .onClick(() => { this.runJs() }) // ... } }
1.4. 总结
本文介绍了在 HarmonyOS NEXT Developer Beta1 版本中,使用 ArkTS 进行混合开发时 Web 组件的基本用法。通过 Web 组件和 WebView API,可以轻松地在应用中嵌入和控制网页内容。以下是几个关键点的总结:
(1)基本语法:通过 Web 组件可以加载和显示网页,处理页面事件,并与网页进行交互。
(2)事件处理:支持多种事件处理,如页面开始加载、加载完成和加载出错、访问历史记录等。
(3)API 使用:WebView API 提供了丰富的方法,用于充当 Bridge 代理,更好和网页端进行通信。
通过本文的介绍,你应该能够初步掌握在 ArkTS 中使用 Web 组件的基本方法,并应用于实际开发中。混合开发可以让你充分利用 Web 技术的优势,同时享受 ArkTS 的高效开发体验。