当前位置: 首页 > news >正文

鸿蒙技术分享:敲鸿蒙木鱼,积____功德——鸿蒙元服务开发:从入门到放弃(3)...

本文是系列文章,其他文章见:
敲鸿蒙木鱼,积____功德🐶🐶🐶——鸿蒙元服务开发:从入门到放弃(1)
敲鸿蒙木鱼,积____功德🐶🐶🐶——鸿蒙元服务开发:从入门到放弃(2)

本文完整源码查看funny-widget

简介

因为工作需要,准备开发元服务,所以就想着搞一个电子木鱼的DEMO学习一下元服务以及桌面卡片的功能开发知识。

详细了解HarmonyOS的元服务,可查看官方介绍

涉及知识点

  • 元服务开发流程
  • 加载图片
  • 播放音频
  • 开发调试
  • 组件代码在卡片和元服务间共享
  • 数据在卡片和元服务间共享

应用内卡片开发

因为元服务卡片存在音频播放问题,在咨询了官方技术支持后,确定是无法播放的。

在官方文档中看到了使用call事件拉起指定UIAbility到后台

因此使用了此方法进行音频播放功能验证。

卡片代码
@Entry
@Component
struct WidgetEventCallCard {@LocalStorageProp('formId') formId: string = '12400633174999288';build() {Column() {Row() {Column() {Button() {Text('playLocalSound').padding(16)}.onClick(() => {console.info('click playLocalSound')postCardAction(this, {action: 'call',abilityName: 'WidgetEventCallEntryAbility', // 只能跳转到当前应用下的UIAbilityparams: {formId: '12400633174999288',method: 'playLocalSound' // 在EntryAbility中调用的方法名}});console.info('after click playLocalSound')})Button() {Text('playOnlineSound').padding(16)}.onClick(() => {console.info('click playOnlineSound')postCardAction(this, {action: 'call',abilityName: 'WidgetEventCallEntryAbility', // 只能跳转到当前应用下的UIAbilityparams: {formId: '12400633174999288',method: 'playOnlineSound' // 在EntryAbility中调用的方法名}});console.info('after click playOnlineSound')})}}.width('100%').height('80%').justifyContent(FlexAlign.Center)}.width('100%').height('100%').alignItems(HorizontalAlign.Center)}
}

卡片上添加了两个按钮分别用来测试本地音频播放和在线音频播放。

Entry代码
import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit';
import { promptAction, window } from '@kit.ArkUI';
import { BusinessError } from '@kit.BasicServicesKit';
import { rpc } from '@kit.IPCKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
import { media } from '@kit.MediaKit';
import { AudioManager } from '../utils/AudioManager';const TAG: string = 'WidgetEventCallEntryAbility';
const DOMAIN_NUMBER: number = 0xFF00;
const CONST_NUMBER_1: number = 1;
const CONST_NUMBER_2: number = 2;class MyParcelable implements rpc.Parcelable {num: number;str: string;constructor(num: number, str: string) {this.num = num;this.str = str;}marshalling(messageSequence: rpc.MessageSequence): boolean {messageSequence.writeInt(this.num);messageSequence.writeString(this.str);return true;}unmarshalling(messageSequence: rpc.MessageSequence): boolean {this.num = messageSequence.readInt();this.str = messageSequence.readString();return true;}
}export default class WidgetEventCallEntryAbility extends UIAbility {// 如果UIAbility第一次启动,在收到call事件后会触发onCreate生命周期回调onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {try {// 监听call事件所需的方法this.callee.on('playLocalSound', (data: rpc.MessageSequence) => {// 获取call事件中传递的所有参数hilog.info(DOMAIN_NUMBER, TAG, `playLocalSound param:  ${JSON.stringify(data.readString())}`);AudioManager.shared.playSound()return new MyParcelable(CONST_NUMBER_1, 'aaa');});this.callee.on('playOnlineSound', (data: rpc.MessageSequence) => {// 获取call事件中传递的所有参数hilog.info(DOMAIN_NUMBER, TAG, `playOnlineSound param:  ${JSON.stringify(data.readString())}`);AudioManager.shared.playOnlineSound()return new MyParcelable(CONST_NUMBER_1, 'aaa');});} catch (err) {hilog.error(DOMAIN_NUMBER, TAG, `Failed to register callee on. Cause: ${JSON.stringify(err as BusinessError)}`);}}// 进程退出时,解除监听onDestroy(): void | Promise<void> {try {this.callee.off('playLocalSound');this.callee.off('playOnlineSound');} catch (err) {hilog.error(DOMAIN_NUMBER, TAG, `Failed to register callee off. Cause: ${JSON.stringify(err as BusinessError)}`);}}onWindowStageCreate(windowStage: window.WindowStage): void {// Main window is created, set main page for this abilityhilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageCreate');windowStage.loadContent('pages/Index', (err) => {if (err.code) {hilog.error(0x0000, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? '');return;}hilog.info(0x0000, 'testTag', 'Succeeded in loading the content.');});}onWindowStageDestroy(): void {// Main window is destroyed, release UI related resourceshilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageDestroy');}onForeground(): void {// Ability has brought to foregroundhilog.info(0x0000, 'testTag', '%{public}s', 'Ability onForeground');}onBackground(): void {// Ability has back to backgroundhilog.info(0x0000, 'testTag', '%{public}s', 'Ability onBackground');}
}

主要就是在onCreate方法中监听playLocalSoundplayOnlineSound两个call事件。

AudioManager
import { media } from '@kit.MediaKit'
import { BusinessError } from '@kit.BasicServicesKit'
import { hilog } from '@kit.PerformanceAnalysisKit'
import { common } from '@kit.AbilityKit'export class AudioManager {static shared = new AudioManager()playSound() {this.log(`AudioManager playSound`)this.playLocalSound()}log(message: string) {hilog.info(0x0000, '音频', '%{public}s', `${message}`);console.info(`[音频]${message}`);}isSeek: boolean = falsecount: number = 0setAVPlayerCallback(avPlayer: media.AVPlayer) {this.log('setAVPlayerCallback')// seek操作结果回调函数avPlayer.on('seekDone', (seekDoneTime: number) => {this.log(`AVPlayer seek succeeded, seek time is ${seekDoneTime}`)})// error回调监听函数,当avPlayer在操作过程中出现错误时调用 reset接口触发重置流程avPlayer.on('error', (err: BusinessError) => {this.log(`avPlayer on error, code is ${err.code}, message is ${err.message}`)avPlayer.reset(); // 调用reset重置资源,触发idle状态})// 状态机变化回调函数avPlayer.on('stateChange', async (state: media.AVPlayerState, reason: media.StateChangeReason) => {this.log(`stateChange回调:${state}${reason.toString()}`)switch (state) {case 'idle': // 成功调用reset接口后触发该状态机上报console.info('AVPlayer state idle called.');avPlayer.release(); // 调用release接口销毁实例对象break;case 'initialized': // avplayer 设置播放源后触发该状态上报console.info('AVPlayer state initialized called.');avPlayer.prepare();break;case 'prepared': // prepare调用成功后上报该状态机console.info('AVPlayer state prepared called.');avPlayer.play(); // 调用播放接口开始播放break;case 'playing': // play成功调用后触发该状态机上报console.info('AVPlayer state playing called.');if (this.count !== 0) {if (this.isSeek) {console.info('AVPlayer start to seek.');avPlayer.seek(avPlayer.duration); //seek到音频末尾} else {// 当播放模式不支持seek操作时继续播放到结尾console.info('AVPlayer wait to play end.');}} else {avPlayer.pause(); // 调用暂停接口暂停播放}this.count++;break;case 'paused': // pause成功调用后触发该状态机上报console.info('AVPlayer state paused called.');avPlayer.play(); // 再次播放接口开始播放break;case 'completed': // 播放结束后触发该状态机上报console.info('AVPlayer state completed called.');avPlayer.stop(); //调用播放结束接口break;case 'stopped': // stop接口成功调用后触发该状态机上报console.info('AVPlayer state stopped called.');avPlayer.reset(); // 调用reset接口初始化avplayer状态break;case 'released':console.info('AVPlayer state released called.');break;case 'error':console.info('AVPlayer state error called.');break;default:console.info('AVPlayer state unknown called.');break;}})}async playLocalSound() {hilog.info(0x0000, '音频', '%{public}s', 'playLocalSound');console.debug(`[音频]playLocalSound`)try {// 创建avPlayer实例对象let avPlayer: media.AVPlayer = await media.createAVPlayer();this.log(`createAVPlayer success`)// 创建状态机变化回调函数this.setAVPlayerCallback(avPlayer);// 通过UIAbilityContext的resourceManager成员的getRawFd接口获取媒体资源播放地址// 返回类型为{fd,offset,length},fd为HAP包fd地址,offset为媒体资源偏移量,length为播放长度let context = getContext(this) as common.ExtensionContext;this.log(`getContext:context=${context}`)// hilog.info(0x0000, '组件', '%{public}s', `playLocalSound:context扩展名=${context.extensionAbilityInfo.name}}`);let fileDescriptor = await context.resourceManager.getRawFd('dang.mp3');this.log(`playLocalSound:fileDescriptor.length=${fileDescriptor.length}}`)let avFileDescriptor: media.AVFileDescriptor ={ fd: fileDescriptor.fd, offset: fileDescriptor.offset, length: fileDescriptor.length };this.isSeek = true; // 支持seek操作// 为fdSrc赋值触发initialized状态机上报avPlayer.fdSrc = avFileDescriptor;} catch (e) {this.log(`playLocalSound出错:${e.toString()}`)}}// 以下demo为通过url设置网络地址来实现播放直播码流的demoasync playOnlineSound() {this.log(`playOnlineSound`)// 创建avPlayer实例对象let avPlayer: media.AVPlayer = await media.createAVPlayer();this.log(`createAVPlayer success`)// 创建状态机变化回调函数this.setAVPlayerCallback(avPlayer);avPlayer.url = 'https://clemmensen.top/static/muyu.mp3';}
}

音频播放代码提供了本地和在线两个播放逻辑。

结论

期望:
在应用未运行的情况下,用卡片call方法拉起App到后台并播放音频。

实测:
播放rawfile音频失败
播放在线音频成功

向官方技术支持咨询:

鸿蒙技术支持
开发者你好,这边测试本地是有音频的敲击声啊159******50
要先杀掉应用再点;正常场景是“应用未运行的情况下”点击卡片播放音频鸿蒙技术支持
确实是的,正在内部分析中鸿蒙技术支持
开发者你好,初步结论是 let context = getContext(this) as common.ExtensionContext;
此场景中上述context获取不到,导致不能读取rawfile文件159******50
这个我清楚,日志就能看到的。问题是怎么解决呢?有没有其他方案读取rawfile?鸿蒙技术支持
当前卡片框架不支持获取context,这个确认为当前规格159******50let context = getContext(this) as common.ExtensionContext;”这个已经是通过call事件拉起application,走到application的代码里了,不算卡片代码的运行环境吧?鸿蒙技术支持
开发者你好,通过call事件去拉起还是借助卡片的能力,卡片里面本身就是受限功能
总结与体会

😅😅😅在鸿蒙系统初期开发这类小众功能真的是遍地是坑。
普通的应用界面因为有大量应用在开发,所以坑填的相对快(但也不少),像元服务、卡片、音频,这样的混合领域,坑得数量可能超出你预期,开发者在前期做开发计划时尽量保守一些,不要脑袋一热,用iOS/Android的开发经验来轻率定时间进度。

相关文章:

鸿蒙技术分享:敲鸿蒙木鱼,积____功德——鸿蒙元服务开发:从入门到放弃(3)...

本文是系列文章&#xff0c;其他文章见&#xff1a;敲鸿蒙木鱼&#xff0c;积____功德&#x1f436;&#x1f436;&#x1f436;——鸿蒙元服务开发&#xff1a;从入门到放弃(1)敲鸿蒙木鱼&#xff0c;积____功德&#x1f436;&#x1f436;&#x1f436;——鸿蒙元服务开发&am…...

Hadoop生态圈框架部署 伪集群版(六)- MySQL安装配置

文章目录 前言一、MySQL安装与配置1. 安装MySQL2. 安装MySQL服务器3. 启动MySQL服务并设置开机自启动4. 修改MySQL初始密码登录5. 设置允许MySQL远程登录6. 登录MySQL 卸载1. 停止MySQL服务2. 卸载MySQL软件包3. 删除MySQL配置文件及数据目录 前言 在本文中&#xff0c;我们将…...

【Docker】创建Docker并部署Web站点

要在服务器上创建Docker容器&#xff0c;并在其中部署站点&#xff0c;你可以按照以下步骤操作。我们将以Flask应用为例来说明如何完成这一过程。 1. 准备工作 确保你的服务器已经安装了Docker。如果没有&#xff0c;请根据官方文档安装&#xff1a; Docker 安装指南 2. 创…...

实验七 用 MATLAB 设计 FIR 数字滤波器

实验目的 加深对窗函数法设计 FIR 数字滤波器的基本原理的理解。 学习用 Matlab 语言的窗函数法编写设计 FIR 数字滤波器的程序。 了解 Matlab 语言有关窗函数法设计 FIR 数字滤波器的常用函数用法。 掌握 FIR 滤波器的快速卷积实现原理。 不同滤波器的设计方法具有不同的优…...

学习ESP32开发板安装鸿蒙操作系统(新板子esp32c3不支持)

鸿蒙LiteOS网址&#xff1a;LiteOS: Huawei LiteOS开源代码官方主仓库.LiteOS Studio 开发工具请访问https://gitee.com/LiteOS/LiteOS_Studio 失败的实践记录见&#xff1a;完全按照手册win10里装Ubuntu 虚拟机然后编译ESP32&#xff08;主要是想针对ESP32C3和S3&#xff09;…...

asp.net core过滤器应用

筛选器类型 授权筛选器 授权过滤器是过滤器管道的第一个被执行的过滤器&#xff0c;用于系统授权。一般不会编写自定义的授权过滤器&#xff0c;而是配置授权策略或编写自定义授权策略。简单举个例子。 using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCo…...

力扣面试题 31 - 特定深度节点链表 C语言解法

题目&#xff1a; 给定一棵二叉树&#xff0c;设计一个算法&#xff0c;创建含有某一深度上所有节点的链表&#xff08;比如&#xff0c;若一棵树的深度为 D&#xff0c;则会创建出 D 个链表&#xff09;。返回一个包含所有深度的链表的数组。 示例&#xff1a; 输入&#xf…...

WordPress阅读文章显示太慢的处理

有两种方式&#xff0c; 1. 完全静态化。 动态都变成html&#xff0c;不再查数据库就快了。 但尝试了几个插件&#xff0c;都未成功。算了后面再研究。 2. cache缓存 用了WP Super Cache测试了一下&#xff0c;打开过一次后&#xff0c;文章秒开&#xff0c;也算达到了要求…...

关于多个线程共享一个实例对象

在多线程环境中&#xff0c;多个线程可能同时调用同一个对象的实例方法&#xff0c;这时候需要考虑如何保证线程安全。理解不同场景下的线程安全性是至关重要的&#xff0c;特别是当方法涉及共享状态时。 1. 共享实例与方法执行 共享实例&#xff1a;多个线程共享同一个实例对…...

【C++】printf 函数详解与格式化输出控制

博客主页&#xff1a; [小ᶻ☡꙳ᵃⁱᵍᶜ꙳] 本文专栏: C 文章目录 &#x1f4af;前言&#x1f4af;printf 基础用法1.1 printf 的常见占位符1.2 占位符与参数的对应关系1.3 换行控制示例&#xff1a; &#x1f4af;格式化输出控制2.1 输出宽度控制2.1.1 指定最小宽度 2.2 …...

HDFS 操作命令

在现代的企业环境中&#xff0c;单机容量往往无法存储大量数据&#xff0c;需要跨机器存储。统一管理分布在 集群上的文件系统称为 分布式文件系统 。 HDFS &#xff08; Hadoop Distributed File System &#xff09;是 Apache Hadoop 项目的一个子项目&#xff0c; Hadoo…...

html ul li 首页渲染多条数据 但只展示八条,其余的数据全部隐藏,通过icon图标 进行展示

<div style"float: left;" id"showMore"> 展开 </div> <div style"float: left;“id"hideLess"> 收起 </div> var data document.querySelectorAll(.allbox .item h3 a); const list document.querySelectorAl…...

Facebook:筑牢隐私安全堡垒,守护社交净土

在全球社交媒体平台中&#xff0c;Facebook一直是风靡全球的佼佼者。然而&#xff0c;随着数字化信息的迅速膨胀&#xff0c;用户隐私保护的重要性日益凸显。面对用户对数据安全性的高度重视&#xff0c;Facebook致力于通过一系列措施来确保隐私保护&#xff0c;守护每位用户的…...

2024年构建PHP应用开发环境

文章目录 前言选择合适的PHP版本安装与配置PHP环境Windows平台Linux平台macOS平台 集成Web服务器数据库连接与管理使用Composer进行依赖管理调试工具的选择代码质量管理部署与持续集成安全性考虑参考资料结语 前言 随着互联网的发展&#xff0c;PHP作为一门成熟的服务器端编程…...

Apache Commons Chain 与 Spring Boot 整合:构建用户注册处理链

文章目录 概述1. 环境准备2. 创建自定义上下文3. 创建命令验证用户输入保存用户数据发送欢迎邮件 4. 构建并执行处理链5. 使用处理链6. 运行结果7. 总结 概述 本文档旨在展示如何在 Spring Boot 应用中使用 Apache Commons Chain 来实现一个用户注册的处理链。我们将通过 Chai…...

一、测试工具LoadRunner Professional脚本编写-录制前设置

设置基于URL的脚本 原因:基于HTML的脚本会导致login接口不能正确录制 设置UTF-8 原因:不勾选此项会导致脚本中文变为乱码...

React Native 组件详解之SectionList、StatusBar、Switch、Text 、 TextInput

在本文中&#xff0c;我们将详细介绍 React Native 中的五个常用组件&#xff1a;SectionList、StatusBar、Switch、Text 和 TextInput。每个组件都有其独特的用途和特性&#xff0c;我们将通过示例代码和 API 说明来帮助你更好地理解和使用它们。 SectionList SectionList 是…...

阿里云:aliyun-cli和ali-instance-cli

概念&#xff1a; 这篇文章只是来澄清一下这俩“cli"之间的区别和联系&#xff1a; aliyun cli 和 ali-instance-cli 都是阿里云提供的命令行工具&#xff0c;但它们的功能和使用场景有所不同。 1. aliyun cli 是一个通用的阿里云命令行接口工具&#xff0c;它允许用户…...

Linux 远程连接服务

远程连接服务器简介 什么是远程连接服务器 远程连接服务器通过文字或图形接口方式来远程登录系统&#xff0c;让你在远程终端前登录linux主机以取得可操 作主机接口&#xff08;shell&#xff09;&#xff0c;而登录后的操作感觉就像是坐在系统前面一样。 远程连接服务器的功…...

Docker 安装和使用

#Docker 安装和使用 文章目录 1. 安装2. 干掉讨厌的 sudo3. 使用镜像源3.1. 使用 upstart 的系统3.2. 使用 systemd 的系统 4. 基本使用4.1. 容器操作4.2. 镜像操作 5. 网络模式说明5.1. bridge 模式5.2. host 模式5.3. container 模式5.4. none 模式 6. 查看 Docker run 启动参…...

挑战杯推荐项目

“人工智能”创意赛 - 智能艺术创作助手&#xff1a;借助大模型技术&#xff0c;开发能根据用户输入的主题、风格等要求&#xff0c;生成绘画、音乐、文学作品等多种形式艺术创作灵感或初稿的应用&#xff0c;帮助艺术家和创意爱好者激发创意、提高创作效率。 ​ - 个性化梦境…...

Golang dig框架与GraphQL的完美结合

将 Go 的 Dig 依赖注入框架与 GraphQL 结合使用&#xff0c;可以显著提升应用程序的可维护性、可测试性以及灵活性。 Dig 是一个强大的依赖注入容器&#xff0c;能够帮助开发者更好地管理复杂的依赖关系&#xff0c;而 GraphQL 则是一种用于 API 的查询语言&#xff0c;能够提…...

江苏艾立泰跨国资源接力:废料变黄金的绿色供应链革命

在华东塑料包装行业面临限塑令深度调整的背景下&#xff0c;江苏艾立泰以一场跨国资源接力的创新实践&#xff0c;重新定义了绿色供应链的边界。 跨国回收网络&#xff1a;废料变黄金的全球棋局 艾立泰在欧洲、东南亚建立再生塑料回收点&#xff0c;将海外废弃包装箱通过标准…...

CocosCreator 之 JavaScript/TypeScript和Java的相互交互

引擎版本&#xff1a; 3.8.1 语言&#xff1a; JavaScript/TypeScript、C、Java 环境&#xff1a;Window 参考&#xff1a;Java原生反射机制 您好&#xff0c;我是鹤九日&#xff01; 回顾 在上篇文章中&#xff1a;CocosCreator Android项目接入UnityAds 广告SDK。 我们简单讲…...

相机Camera日志分析之三十一:高通Camx HAL十种流程基础分析关键字汇总(后续持续更新中)

【关注我,后续持续新增专题博文,谢谢!!!】 上一篇我们讲了:有对最普通的场景进行各个日志注释讲解,但相机场景太多,日志差异也巨大。后面将展示各种场景下的日志。 通过notepad++打开场景下的日志,通过下列分类关键字搜索,即可清晰的分析不同场景的相机运行流程差异…...

安宝特案例丨Vuzix AR智能眼镜集成专业软件,助力卢森堡医院药房转型,赢得辉瑞创新奖

在Vuzix M400 AR智能眼镜的助力下&#xff0c;卢森堡罗伯特舒曼医院&#xff08;the Robert Schuman Hospitals, HRS&#xff09;凭借在无菌制剂生产流程中引入增强现实技术&#xff08;AR&#xff09;创新项目&#xff0c;荣获了2024年6月7日由卢森堡医院药剂师协会&#xff0…...

FFmpeg:Windows系统小白安装及其使用

一、安装 1.访问官网 Download FFmpeg 2.点击版本目录 3.选择版本点击安装 注意这里选择的是【release buids】&#xff0c;注意左上角标题 例如我安装在目录 F:\FFmpeg 4.解压 5.添加环境变量 把你解压后的bin目录&#xff08;即exe所在文件夹&#xff09;加入系统变量…...

从 GreenPlum 到镜舟数据库:杭银消费金融湖仓一体转型实践

作者&#xff1a;吴岐诗&#xff0c;杭银消费金融大数据应用开发工程师 本文整理自杭银消费金融大数据应用开发工程师在StarRocks Summit Asia 2024的分享 引言&#xff1a;融合数据湖与数仓的创新之路 在数字金融时代&#xff0c;数据已成为金融机构的核心竞争力。杭银消费金…...

阿里云Ubuntu 22.04 64位搭建Flask流程(亲测)

cd /home 进入home盘 安装虚拟环境&#xff1a; 1、安装virtualenv pip install virtualenv 2.创建新的虚拟环境&#xff1a; virtualenv myenv 3、激活虚拟环境&#xff08;激活环境可以在当前环境下安装包&#xff09; source myenv/bin/activate 此时&#xff0c;终端…...

算法250609 高精度

加法 #include<stdio.h> #include<iostream> #include<string.h> #include<math.h> #include<algorithm> using namespace std; char input1[205]; char input2[205]; int main(){while(scanf("%s%s",input1,input2)!EOF){int a[205]…...