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控制器。

javascript 复制代码
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:组件挂载显示后触发此回调(通用事件)。

javascript 复制代码
      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代码等。

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. 新建工程

主要目录结构:

javascript 复制代码
|-- entry
|   |-- src
|   |   |-- main
|   |   |   |-- ets
|   |   |   |   |-- pages
|   |   |   |   |   |-- Index.ets(案例代码)
|-- module.json5

1.3.2. 配置权限

在 module.json5 中配置网络权限:

javascript 复制代码
{
  "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页面:

javascript 复制代码
@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. 页面标题

获取当前加载网页标题,进行动态展示

javascript 复制代码
  // ...
  @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)第一步:定义状态,存储加载中和进度数据

javascript 复制代码
  // 当前访问页面历史记录索引
  historyCurrIndex: number = 0

(2)第二步:添加事件获取历史记录

javascript 复制代码
// ...
.onRefreshAccessedHistory(() => {
// == 网页加载完成可访问页面历史记录 ==
// 获取当前Webview的页面历史记录列表
const history = this.controller.getBackForwardEntries()
// 当前页面历史记录索引
this.historyCurrIndex = history.currentIndex
// 历史记录索引的数量,最多保存50条,超过时起始记录会被覆盖
console.log('mgx', history.size)
})

(3)第三步:在原生返回钩子函数中控制逻辑

javascript 复制代码
  onBackPress() {
    // 在web容器中, 当前页面之前还有页面, 则容器内返回上一页
    if (this.historyCurrIndex > 0) {
      this.controller.backward()
    } else {
    // 返回原生页面
      router.back()
    }
    // 自定义返回逻辑
    return true
  }

1.3.6. 在页面标题中添加菜单图标,点击刷新页面。

(1)第一步:给Navigation组件右侧添加菜单(图标随便)

javascript 复制代码
build() {
  Navigation() {
    // ...
  }
.title(this.title)
.titleMode(NavigationTitleMode.Mini)
.menus(this.titleMenus)
}

(2)第二步:定义菜单 Builder 绑定事件控制刷新

javascript 复制代码
  @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 方法,选择原生相册,获取选择图片结果显示到网页中

javascript 复制代码
interface InjectJs {
  // 测试方法
  test: (a: string) => void
  // 选择相册
  select: () => Promise<string>
}

type InjectKeys = keyof InjectJs

(2)第二步:定义注入的方法和逻辑

javascript 复制代码
  // 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 执行注入

javascript 复制代码
Web({ src: this.src, controller: this.controller })
// ...
.onAppear(() => {
// JSBridge代理注入
this.webInject()
})

(4)第四步:本地创建网页加载

  这里使用的是原生 js 开发网页端,实际开发中可选择 vue 、 react 、 uni-app 等框架开发,效率更高。

  目录:entry/src/main/resources/rawfile/index.html

javascript 复制代码
<!doctype html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>混合开发</title>
    <style>
    * {
      margin: 0;
      padding: 0;
    }

    ul li {
      padding: 10px;
    }
</style>
</head>

<body>
<div>
    <h1>H5页面</h1>
    <div>
        <p>
            <!--     显示选择相册的图片       -->
            <img id="img"
                 src="">
        </p>
    </div>
    <ul>
        <li>
            <button id="test">调用原生test</button>
        </li>
        <li>
            <button id="sel">调用原生select选择相册</button>
        </li>
    </ul>
</div>
<script>
    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)
      })
    }
</script>
</body>

</html>

1.3.8. 执行网页JS

  有时候,我们需要在原生端调用网页中 JS,完成一些业务需求。

(1)第一步:网页中挂载 JS到 window

重新运行项目,点击按钮调用注入的函数测试

javascript 复制代码
<script>
    window.go = () => {
      return '我是网页JS函数'
    }
    window.onload = () => {
      // ...
    }
</script>

(2)原生端调用挂载的 JS

借助 webview 提供runJavaScript api 异步执行JavaScript脚本,并通过回调方式返回脚本执行的结果。

官方参考文档

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 的高效开发体验。

相关推荐
AlienZHOU4 小时前
AI Coding 时代下,我的技术面试实践分享
前端·后端·面试
Captaincc7 小时前
AI用量v0.1.11更新发布 新增 jusage doctor 诊断指令 托盘展示token 和余额 新增 AutoClaw 支持
前端·后端·vibecoding
计算机魔术师9 小时前
德国Wiki被黑后两周,OpenAI终于把模型失控的账本摊开了
前端
kyriewen9 小时前
我让 AI 当面试官面了我一轮:第 3 个追问我就卡住了(附 10 道追问清单)
前端·面试·ai编程
IT_陈寒9 小时前
Python的GIL把我坑惨了,多线程跑得比单线程还慢
前端·人工智能·后端
贾伟康10 小时前
【HarmonyOS 7新能力|026】Agent Framework Kit工程封装:把接入逻辑放进可维护的分层结构
agent·harmonyos·arkts·a2a·harmonyos 7
前端snow10 小时前
ai agent --- 多agent框架之图编排引擎-langgraph
前端
竹林81810 小时前
OmniPic Studio v3.2.1 核心技术架构与全平台发版解析文档
前端·浏览器
JamesZhang8007810 小时前
页面内存只涨不跌? 一次泄漏排查, 牵出 WeakMap 的诞生
前端
Z小明10 小时前
第 6 章 组件进阶
前端·vue.js