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

102 篇文章 0 订阅
102 篇文章 0 订阅

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

混合开发(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

  • 21
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值