【鸿蒙实战开发】混合开发-web组件入门和实战

100 篇文章 1 订阅
100 篇文章 2 订阅

一、混合开发的背景和好处

混合开发(Hybrid Development)是一种结合原生应用和Web应用的开发模式,旨在同时利用两者的优势。随着移动应用需求的多样化和复杂化,单一的开发方式往往难以满足所有需求。混合开发提供了一种灵活、高效的解决方案,特别是在以下方面具有显著的优势:

  1. 跨平台兼容:混合开发允许开发者编写一次代码,并在多个平台(如Android、iOS、HarmonyOS等)上运行。这大大减少了开发和维护成本。

  2. 快速迭代:Web技术(如HTML、CSS、JavaScript)的快速开发和部署能力,使得混合应用可以更快地进行迭代和更新。

  3. 丰富的Web生态:借助丰富的Web生态系统,开发者可以利用大量现有的Web库和框架,快速实现复杂功能。

  4. 原生性能:通过将关键功能部分使用原生代码实现,混合应用可以在保证性能的同时,享受Web开发的灵活性。

二、混合开发应用的结构

为了更好地理解混合开发的概念,以下是一张示意图,展示了混合开发架构中原生代码与Web代码的结合。

图中展示了混合开发应用的结构,其中包括:

  • 原生层(Native Layer):包括操作系统(如HarmonyOS)、设备硬件和原生API,提供高性能和底层功能支持。
  • Web层(Web Layer):包括HTML、CSS、JavaScript等Web技术,负责应用的界面和逻辑部分。
  • 桥接层(Bridge Layer):连接原生层和Web层,允许两者之间的数据和功能交互。

通过这种架构,开发者可以在Web层快速构建界面和业务逻辑,同时利用原生层提供的高性能和丰富功能,实现混合开发的最佳效果。

在HarmonyOS NEXT Developer Beta1版本中,ArkTS 提供了强大的混合开发能力,允许开发者在应用中嵌入 Web 组件,利用 Web 技术构建应用的一部分。接下来,我将介绍如何在 ArkTS 中使用 Web 组件。

三、ArkTS Web 组件教程

一、语法说明

在 ArkTS 中,混合开发主要通过 Web 组件和 WebView API 来实现。通过这些工具,可以加载和显示网页内容,进行页面控制和数据交互。

1. 基本语法

Web 组件用于在界面中嵌入一个网页浏览器。它的常用属性包括:

  • src:指定要加载的网页资源地址。
  • controller:webview控制器。
src: ResourceStr = 'https://m.jd.com'

Web({ src: this.src, controller: this.controller })

注意:访问在线网页时需添加网络权限:ohos.permission.INTERNET

2. 事件处理

Web 组件支持多种事件,如:

  • onPageBegin:页面开始加载时触发。
  • onPageEnd:页面加载完成时触发。
  • onErrorReceive:页面加载出错时触发。
  • onTitleReceive:网页document标题更改时触发该回调。
  • onProgressChange:网页加载进度变化时触发该回调。
  • onRefreshAccessedHistory:加载网页页面完成时触发该回调,用于应用更新其访问的历史链接。
  • onAppear:组件挂载显示后触发此回调(通用事件)。
      Web({ src: this.src, controller: this.controller })
          .onProgressChange((data) => {
          })
          .onPageBegin(() => {
          })
          .onPageEnd(() => {
          })
          .onErrorReceive((e) => {
          })
          .onTitleReceive((data) => {
          })
          .onRefreshAccessedHistory(() => {
          })
          .onAppear(() => {
          })
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 })
四、实战案例

下面是一个完整的小案例,展示如何在 ArkTS 中使用 Web 组件加载网页、处理页面事件,并与网页进行交互。

开发环境

DevEco Studio NEXT Developer Beta1
构建版本:5.0.3.403, built on June 20, 2024
Runtime version: 17.0.10+1-b1087.17 aarch64

1. 新建工程

主要目录结构:

|-- entry
|   |-- src
|   |   |-- main
|   |   |   |-- ets
|   |   |   |   |-- pages
|   |   |   |   |   |-- Index.ets(案例代码)
|-- module.json5
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": [
       // ...
    ]
  }
}
3. 加载页面

在 Index.ets 文件中,使用 Web 组件和 WebView API,加载京东商城首页:

@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)

 }

}

效果如图:

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)

 }

效果如图:

5. 页面进度

获取页面进度数据,添加进度条效果:

第一步:定义状态,存储加载中和进度数据
  // 网页是否在加载中
  @State loading: boolean = true
  // 网页加载进度
  @State progress: number = 0

第二步:添加进度条结构
build() {
  Navigation() {
    Stack({ alignContent: Alignment.Top }) {
    // 加载中显示进度条
    if (this.loading) {
      Progress({ type: ProgressType.Linear, value: this.progress, total: 100 })
        .style({ strokeWidth: 3, enableSmoothEffect: true })
        .color('#fd1800')
        .zIndex(1)
    }
    Web({ src: this.src, controller: this.controller })
   .onTitleReceive((data) => {
        this.title = data?.title || '--'
    })
   }
  .width('100%')
  .layoutWeight(1)
  }
.title(this.title)
.titleMode(NavigationTitleMode.Mini)

}

第三步:添加事件获取进度数据
Web({ src: this.src, controller: this.controller })
  .onProgressChange((data) => {
    if (data) {
      // 记录加载进度
      this.progress = data.newProgress
      if (data.newProgress === 100) {
        // 加载完成进度条消失
        animateTo({ duration: 300, delay: 200 }, () => {
          this.loading = false
        })
      }
    }
  })
  .onPageBegin(() => {
    console.log('mgx', '开始加载网页')
    this.progress = 0
    this.loading = true
  })
  .onPageEnd(() => {
    console.log('mgx', '网页加载完成')
  })

ok,看效果:

6. 页面返回和刷新

下边咱们来获取 web 容器中页面的历史记录进行返回控制和页面刷新操作。 点击左上角返回按钮,如果容器中页面有历史记录,则返回历史页面;否则,返回原生记录页面。

第一步:定义状态,存储当前历史记录索引
  // 当前访问页面历史记录索引
  historyCurrIndex: number = 0

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

第三步:在原生返回钩子函数中控制逻辑
  onBackPress() {
    // 在web容器中, 当前页面之前还有页面, 则容器内返回上一页
    if (this.historyCurrIndex > 0) {
      this.controller.backward()
    } else {
    // 返回原生页面
      router.back()
    }
    // 自定义返回逻辑
    return true
  }

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

第一步:给Navigation组件右侧添加菜单(图标随便)
build() {
  Navigation() {
    // ...
  }
.title(this.title)
.titleMode(NavigationTitleMode.Mini)
.menus(this.titleMenus)
}

第二步:定义菜单 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)
  }

7. JSBridge代理

接下来,进入重点环节,我们来学习实操下如何向 Web 容器的网页中注入 JS 对象,给网页提供原生的能力支持,例如: 选择相册、拍照、传感器等底层能力。

核心依赖webview 提供的registerJavaScriptProxy api,提供了应用与Web组件加载的网页之间强大的交互能力。注入JavaScript对象到window对象中,并在window对象中调用该对象的方法。

第一步:定义注入 JS 的类型

示例注入了两个函数:

  1. test 方法,获取网页调用后传参
  2. select 方法,选择原生相册,获取选择图片结果显示到网页中
interface InjectJs {
  // 测试方法
  test: (a: string) => void
  // 选择相册
  select: () => Promise<string>
}

type InjectKeys = keyof InjectJs

第二步:定义注入的方法和逻辑
  // 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[])
  }

第三步:在通用事件onAppear 执行注入
Web({ src: this.src, controller: this.controller })
// ...
.onAppear(() => {
// JSBridge代理注入
this.webInject()
})

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

这里使用的是原生 js 开发网页端,实际开发中可选择 vue 、 react 、 uni-app 等框架开发,效率更高。
目录:entry/src/main/resources/rawfile/index.html

<!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\. web 组件基本使用
  // src: ResourceStr = 'https://m.jd.com'
  src: ResourceStr = $rawfile('index.html')

效果如图:

第六步:使用eruda插件进行真机调试
<script src="https://cdnjs.cloudflare.com/ajax/libs/eruda/2.4.1/eruda.min.js"></script>
<script>eruda.init()</script>

效果如图:

8. 执行网页JS

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

第一步:网页中挂载 JS到 window
<script>
    window.go = () => {
      return '我是网页JS函数'
    }
    window.onload = () => {
      // ...
    }
</script>

第二步:原生端调用挂载的 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()
        })
   // ...
 }
}

五、总结

本文介绍了在 HarmonyOS NEXT Developer Beta1 版本中,使用 ArkTS 进行混合开发时 Web 组件的基本用法。通过 Web 组件和 WebView API,可以轻松地在应用中嵌入和控制网页内容。以下是几个关键点的总结:

  1. 基本语法:通过 Web 组件可以加载和显示网页,处理页面事件,并与网页进行交互。
  2. 事件处理:支持多种事件处理,如页面开始加载、加载完成和加载出错、访问历史记录等。
  3. API 使用WebView API 提供了丰富的方法,用于充当 Bridge 代理,更好和网页端进行通信。

通过本文的介绍和示例代码,你应该能够初步掌握在 ArkTS 中使用 Web 组件的基本方法,并应用于实际开发中。混合开发可以让你充分利用 Web 技术的优势,同时享受 ArkTS 的高效开发体验。

鸿蒙全栈开发全新学习指南

之前总有很多小伙伴向我反馈说,不知道学习哪些鸿蒙开发技术?不知道需要重点掌握哪些鸿蒙应用开发知识点?而且学习时频繁踩坑,最终浪费大量时间。所以这里为大家准备了一份实用的鸿蒙(HarmonyOS NEXT)学习路线与学习文档用来跟着学习是非常有必要的。

针对一些列因素,整理了一套纯血版鸿蒙(HarmonyOS Next)全栈开发技术的学习路线,包含了鸿蒙开发必掌握的核心知识要点,内容有(ArkTS、ArkUI开发组件、Stage模型、多端部署、分布式应用开发、WebGL、元服务、OpenHarmony多媒体技术、Napi组件、OpenHarmony内核、OpenHarmony驱动开发、系统定制移植等等)鸿蒙(HarmonyOS NEXT)技术知识点。

本路线共分为四个阶段

第一阶段:鸿蒙初中级开发必备技能

在这里插入图片描述

第二阶段:鸿蒙南北双向高工技能基础:gitee.com/MNxiaona/733GH

在这里插入图片描述

第三阶段:应用开发中高级就业技术

第四阶段:全网首发-工业级南向设备开发就业技术:gitee.com/MNxiaona/733GH

在这里插入图片描述

《鸿蒙 (Harmony OS)开发学习手册》(共计892页)

如何快速入门?

1.基本概念
2.构建第一个ArkTS应用
3.……

开发基础知识:gitee.com/MNxiaona/733GH

1.应用基础知识
2.配置文件
3.应用数据管理
4.应用安全管理
5.应用隐私保护
6.三方应用调用管控机制
7.资源分类与访问
8.学习ArkTS语言
9.……

在这里插入图片描述

基于ArkTS 开发

1.Ability开发
2.UI开发
3.公共事件与通知
4.窗口管理
5.媒体
6.安全
7.网络与链接
8.电话服务
9.数据管理
10.后台任务(Background Task)管理
11.设备管理
12.设备使用信息统计
13.DFX
14.国际化开发
15.折叠屏系列
16.……

在这里插入图片描述

鸿蒙开发面试真题(含参考答案):gitee.com/MNxiaona/733GH

在这里插入图片描述

鸿蒙入门教学视频:

美团APP实战开发教学:gitee.com/MNxiaona/733GH

写在最后

  • 如果你觉得这篇内容对你还蛮有帮助,我想邀请你帮我三个小忙:
  • 点赞,转发,有你们的 『点赞和评论』,才是我创造的动力。
  • 关注小编,同时可以期待后续文章ing🚀,不定期分享原创知识。
  • 想要获取更多完整鸿蒙最新学习资源,请移步前往小编:gitee.com/MNxiaona/733GH

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值