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

【HarmonyOS】元服务入门详解 (一)

【HarmonyOS】元服务入门详解 (一)

在这里插入图片描述

一、前言

首先元服务并非是小程序小游戏,也不是之前的快应用。元服务是使用ArkTS语言开发。共同点是与他们相同都是免安装的小安装包应用。

它与其他轻应用的共同点是免安装特性,但在技术实现和生态定位上有本质区别:

1、开发语言:采用ArkTS(鸿蒙生态主推的声明式语言)
2、包体特性:严格限制安装包大小(通常≤10MB),实现秒开体验
3、API体系:使用独立的元服务API集,而非传统应用的系统API
4、分发方式:通过“服务找人”模式,依托系统级卡片、小艺搜索等入口触达用户
5、体验优势:无需安装即可使用核心功能,支持跨设备流转

元服务因为限制安装包大小,并且使用单独的元服务API集,非应用开发直接使用系统API。正因为以上两点,做到了免安装的效果。元服务作为免安装的轻量服务形态,在用户触达和体验上有显著优势。

并且在开发模式上与应用也不同,需要先在AGC平台创建后,才能在IDE进行项目创建进行包名映射绑定。

元服务通过“服务找人”这个概念,使用系统级别的卡片入门,小艺搜索入门,提供系统级别的“小程序”体验。让用户可以在手机上,不用安装App,就能使用功能。

以本文将开发的“舒尔特方格”元服务为例,用户可以通过桌面卡片直接启动游戏,或通过小艺搜索“舒尔特方格”快速调用,无需经历传统应用的下载安装流程。

二、元服务开发前置步骤

1、AGC平台创建元服务项目

元服务开发需先在华为应用市场开发者平台(AGC)完成项目注册,步骤如下:
在这里插入图片描述

  1. 登录AGC平台,进入“我的项目”
  2. 点击“新增项目”,选择“元服务”类型
  3. 填写基本信息(应用名称、包名等),包名需牢记(如"com.atomicservice.691757860316xxxxx", 现在元服务的包名都是固定格式,最后都是appid。所以一定要先在AGC上创建项目)

2、 IDE创建元服务项目

使用DevEco Studio创建元服务项目:
在这里插入图片描述

  1. 新建项目时选择“元服务应用”模板
  2. 输入项目名称(如ShulteTable),选择保存路径
  3. 填写AGC注册的包名,确保与平台一致
  4. 选择设备类型(建议先选“手机”),点击“完成”

项目结构说明:

ShulteTable/
├─ entry/                  // 主模块
│  ├─ src/main/ets/        // ArkTS代码
│  │  ├─ entryability/     // 入口能力
│  │  ├─ pages/            // 页面
│  │  └─ widget/           // 卡片
└─ agconnect-services.json // AGC配置文件

3. 元服务图标设计

元服务需准备两类图标:
(1)应用图标:1024x1024px,用于服务入口。可使用工具进行裁剪:
在entry文件夹,右键New,选择Image Asset:
在这里插入图片描述

(2)卡片图标:根据卡片尺寸设计(如2x2卡片为128x128px)

图标需符合鸿蒙设计规范,建议使用圆角设计,放置在entry/src/main/resources/base/media目录下。

三、元服务UI开发

以舒尔特方格游戏为例,讲解元服务UI开发核心要点。
基于ArkTS的UI开发(页面路由、组件布局、状态管理)

1. 页面路由配置

main_pages.json中配置页面路由:

{"src": ["pages/HomePage","pages/GamePage","pages/UserAgreement"]
}

2. 首页实现(HomePage.ets)

首页包含开始游戏按钮、近期分数和操作指南:

@Entry
@Component
struct HomePage {@State recentScores: ScoreItem[] = [];aboutToAppear() {this.loadScores(); // 加载历史记录}loadScores() {// 从本地存储获取数据const scores = AppStorage.Get('scores') || [];this.recentScores = scores.slice(0, 5);}build() {Column() {// 标题Text('舒尔特方格').fontSize(32).fontWeight(FontWeight.Bold).margin(20)// 开始按钮Button('开始游戏').width('80%').height(50).backgroundColor('#007DFF').fontColor('#FFFFFF').onClick(() => router.pushUrl({ url: 'pages/GamePage' })).margin(10)// 近期分数Column() {Text('近期成绩').fontSize(18).margin(10)List() {ForEach(this.recentScores, (item) => {ListItem() {Row() {Text(`${item.size}x${item.size}`).width(60)Text(item.time).flexGrow(1)Text(item.date).width(80)}.padding(10)}})}}.backgroundColor('#FFFFFF').borderRadius(10).padding(5).margin(10).width('90%')}.width('100%').height('100%').backgroundColor('#F5F5F5')}
}interface ScoreItem {size: number;time: string;date: string;
}

3. 游戏页面实现(GamePage.ets)

核心游戏逻辑包含网格生成、计时和点击判断:

@Entry
@Component
struct GamePage {@State gridSize: number = 5;@State numbers: number[] = [];@State current: number = 1;@State timer: string = '00:00';@State timerId: number = 0;aboutToAppear() {this.initGrid();}initGrid() {// 生成1~n²的随机序列const total = this.gridSize * this.gridSize;this.numbers = Array.from({ length: total }, (_, i) => i + 1).sort(() => Math.random() - 0.5);this.current = 1;}startTimer() {const startTime = Date.now();this.timerId = setInterval(() => {const elapsed = Math.floor((Date.now() - startTime) / 1000);this.timer = `${Math.floor(elapsed / 60).toString().padStart(2, '0')}:${(elapsed % 60).toString().padStart(2, '0')}`;}, 1000);}build() {Column() {// 计时器Text(`用时: ${this.timer}`).fontSize(20).margin(10)// 网格选择Scroll({ direction: Axis.Horizontal }) {Row() {ForEach([3, 4, 5, 6, 7], (size) => {Button(`${size}x${size}`).onClick(() => this.gridSize = size).margin(5).backgroundColor(this.gridSize === size ? '#007DFF' : '#F5F5F5')})}.padding(5)}// 游戏网格Grid() {ForEach(this.numbers, (num) => {GridItem() {Button(num.toString()).width('100%').height('100%').backgroundColor(num === this.current ? '#007DFF' : num < this.current ? '#90EE90' : '#EEEEEE').onClick(() => {if (num === this.current) {if (this.current === this.gridSize * this.gridSize) {// 游戏结束clearInterval(this.timerId);this.saveScore(); // 保存成绩} else {this.current++;}}})}})}.columnsTemplate(Array(this.gridSize).fill('1fr').join(' ')).rowsTemplate(Array(this.gridSize).fill('1fr').join(' ')).width('90%').height('60%')}.width('100%').padding(10)}
}

四、元服务卡片开发

元服务卡片可直接在桌面显示关键信息,支持快速交互。
桌面卡片的创建与数据交互。

1. 卡片配置(widget.json)

{"forms": [{"name": "ShulteWidget","description": "舒尔特方格快捷卡片","src": "./ShulteWidget.ets","window": {"designWidth": 720,"autoDesignWidth": true},"colorMode": "auto","isDefault": true,"updateEnabled": true,"scheduledUpdateTime": "00:00","updateDuration": 1}]
}

2. 卡片实现(ShulteWidget.ets)

在这里插入图片描述

@Entry
@Component
struct ShulteWidget {private formProvider: FormProvider = new FormProvider();build() {Column() {Text('舒尔特方格').fontSize(16).margin(5)Button('快速开始').width('80%').height(30).fontSize(14).backgroundColor('#007DFF').onClick(() => {// 打开元服务this.formProvider.startAbility({bundleName: 'com.example.shultetable',abilityName: 'EntryAbility'});})Text('最佳记录: 01:25').fontSize(12).margin(5).fontColor('#666666')}.width('100%').height('100%').backgroundColor('#FFFFFF').padding(10)}
}

3. 卡片数据更新

通过FormExtensionAbility实现卡片数据刷新:

export default class ShulteFormAbility extends FormExtensionAbility {onUpdate(formId: string) {// 获取最新成绩const bestScore = AppStorage.Get('bestScore') || '00:00';// 更新卡片数据this.updateForm(formId, {'bestScore': bestScore});}
}

五、源码示例

在这里插入图片描述

// pages/HomePage.ets
import router from '@ohos.router';
import promptAction from '@ohos.promptAction';@Entry
@Component
struct HomePage {@State recentScores: ScoreItem[] = [];@State isLoading: boolean = true;aboutToAppear() {this.loadRecentScores();}// 加载最近5次游戏记录loadRecentScores() {// 模拟从本地存储加载数据setTimeout(() => {// 示例数据,实际应从AppStorage获取this.recentScores = [{ id: 1, time: '01:25', date: '2025-07-14', gridSize: 5 },{ id: 2, time: '01:40', date: '2025-07-13', gridSize: 5 },{ id: 3, time: '01:55', date: '2025-07-12', gridSize: 5 },{ id: 4, time: '02:10', date: '2025-07-10', gridSize: 5 },{ id: 5, time: '02:30', date: '2025-07-09', gridSize: 5 },];this.isLoading = false;}, 500);}// 格式化日期显示formatDate(dateStr: string): string {const date = new Date(dateStr);return `${date.getMonth() + 1}/${date.getDate()}`;}// 导航到游戏页面navigateToGame() {router.pushUrl({url: 'pages/GamePage'});}@Builder TitleView(){// 顶部导航栏Row() {Button() {// Image($r('app.media.back_arrow')) // 需要准备返回箭头图标//   .width(24)//   .height(24)}.onClick(() => router.back()).margin({ left: 15 })Text('专注力训练').fontSize(20).fontWeight(FontWeight.Bold).fontColor('#007DFF').margin({ left: 20 })}.width('100%').height(50).backgroundColor('#FFFFFF')}/*** 用户头像view*/@Builder UserInfoView(){Column(){// 用户头像Image($r("app.media.icon")).width(px2vp(200)).height(px2vp(200))// 昵称// 设置按钮Button("设置").onClick(()=>{router.pushUrl({url: 'pages/AuthPage'})})}.margin({ bottom: 30, top: 20 })}build() {Column(){this.TitleView()Column() {this.UserInfoView()// 开始游戏按钮Button('开始游戏').width('80%').height(50).backgroundColor('#007DFF').fontColor('#FFFFFF').fontSize(18).onClick(() => this.navigateToGame()).margin({ bottom: 30, top: 20 }).borderRadius(25).hoverEffect(HoverEffect.Auto)// 近期分数卡片Column() {Text('近期分数').fontSize(20).fontWeight(FontWeight.Bold).margin({ left: 15, top: 10, bottom: 10 }).width('100%')if (this.isLoading) {LoadingProgress().color('#007DFF').width(50).height(50).margin({ top: 20, bottom: 20 })} else if (this.recentScores.length === 0) {Text('暂无游戏记录').fontSize(16).fontColor('#999999').margin({ top: 20, bottom: 20 })} else {List() {ForEach(this.recentScores, (score: ScoreItem) => {ListItem() {Row() {Text(`${score.gridSize}x${score.gridSize}`).fontSize(16).width(70).textAlign(TextAlign.Center).fontColor('#007DFF').backgroundColor('#E6F4FF').margin({ right: 10 }).padding(5).borderRadius(8)Column() {Text(score.time).fontSize(18).fontWeight(FontWeight.Bold).width('100%').textAlign(TextAlign.Start)Text(this.formatDate(score.date)).fontSize(12).fontColor('#999999').width('100%').textAlign(TextAlign.Start)}.width('100%')}.width('100%').height(60).padding({ left: 10, right: 10 })}})}.width('100%').height(220).margin({ bottom: 20 })}}.width('90%').backgroundColor('#FFFFFF').borderRadius(15).margin({ bottom: 20 })// 游戏操作指南卡片Column() {Text('游戏操作指南').fontSize(20).fontWeight(FontWeight.Bold).margin({ left: 15, top: 10, bottom: 10 }).width('100%')Column() {GuideItem({index: 1,content: '选择网格大小'})GuideItem({index: 2,content: '点击"开始游戏"按钮'})GuideItem({index: 3,content: '按照数字顺序点击网格中的数字'})GuideItem({index: 4,content: '完成所有数字点击后,游戏结束并显示用时'})GuideItem({index: 5,content: '点击"重置游戏"可重新开始'})}.width('100%').padding(15)}.width('90%').backgroundColor('#FFFFFF').borderRadius(15).margin({ bottom: 30 })}.width('100%').height('100%').padding({  left: 15, right: 15 }).backgroundColor('#F8F9FA')}.height(px2vp(2000)).width("100%")// Scroll(){//// }// .width("100%")// .height("100%")// .scrollable(ScrollDirection.Vertical)}
}// 指南项组件
@Component
struct GuideItem {index: number = 0;content: string = '';build() {Row() {Text(`${this.index}`).fontSize(16).fontWeight(FontWeight.Bold).width(28).height(28).textAlign(TextAlign.Center).backgroundColor('#007DFF').fontColor('#FFFFFF').borderRadius(14).margin({ right: 10 })Text(this.content).fontSize(16).width('90%')}.width('100%').margin({ bottom: 15 })}
}// 分数项接口
interface ScoreItem {id: number;time: string;date: string;gridSize: number;
}

在这里插入图片描述


import promptAction from '@ohos.promptAction';@Entry
@Component
struct GamePage {// 网格数@State gridSize: number = 2; // 默认5x5网格@State gridData: number[] = [];@State currentNumber: number = 1;@State isGameStarted: boolean = false;@State isGameFinished: boolean = false;@State timer: string = '00:00';@State startTime: number = 0;@State timerId: number = 0;@State bestTime: string = '00:00';aboutToAppear() {this.initGrid();const savedBestTime: string = AppStorage.get('bestTime') ?? "00:00";if (savedBestTime) {this.bestTime = savedBestTime;}}/*** 初始化网格数据*/initGrid() {const totalCells = this.gridSize * this.gridSize;this.gridData = [];for (let i = 1; i <= totalCells; i++) {this.gridData.push(i);}// 打乱顺序this.gridData.sort(() => Math.random() - 0.5);this.currentNumber = 1;this.isGameFinished = false;console.log("wppDebug", " list: " + JSON.stringify(this.gridData));}// 开始游戏startGame() {this.isGameStarted = true;this.startTime = Date.now();this.timerId = setInterval(() => {const elapsedTime = Date.now() - this.startTime;const seconds = Math.floor(elapsedTime / 1000) % 60;const minutes = Math.floor(elapsedTime / 60000);this.timer = `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;}, 1000);}// 处理格子点击handleCellClick(number: number) {if (!this.isGameStarted || this.isGameFinished) {return;}if (number === this.currentNumber) {if (number === this.gridSize * this.gridSize) {// 游戏完成this.isGameFinished = true;clearInterval(this.timerId);this.checkBestTime();promptAction.showToast({ message: '恭喜,你完成了游戏!' });} else {this.currentNumber++;}}}// 检查是否是最佳时间checkBestTime() {const currentTime = this.timer;if (this.bestTime === '00:00' || this.compareTime(currentTime, this.bestTime)) {this.bestTime = currentTime;AppStorage.set('bestTime', currentTime);}}compareTime(time1: string, time2: string): number {// 假设日期为同一天(如2000-01-01)const date1 = new Date(`2000-01-01T${time1}:00`);const date2 = new Date(`2000-01-01T${time2}:00`);// 比较时间戳return date1.getTime() - date2.getTime();}// 重置游戏resetGame() {clearInterval(this.timerId);this.initGrid();this.isGameStarted = false;this.timer = '00:00';}// 改变网格大小changeGridSize(size: number) {if (this.isGameStarted && !this.isGameFinished) {promptAction.showToast({ message: '请先完成当前游戏' });return;}this.gridSize = size;this.initGrid();}// 按钮格子数@State buttonList: Array<string> = ['2x2', '3x3', '4x4', '5x5', '6x6', '7x7']; //, '8x8', '9x9'@Builder ItemButtonView(){Button('5x5').onClick(() => this.changeGridSize(5)).margin(5).enabled(!this.isGameStarted || this.isGameFinished).backgroundColor(this.gridSize === 5 ? '#007DFF' : '#F5F5F5').fontColor(this.gridSize === 5 ? '#FFFFFF' : '#000000')}/*** 格子列表*/@Builder ButtonListView(){Scroll(){Row() {ForEach(this.buttonList, (item: string, index: number) => {Button(item).onClick(() => this.changeGridSize(index + 2)).margin(5).enabled(!this.isGameStarted || this.isGameFinished).backgroundColor(this.gridSize === (index + 2) ? '#007DFF' : '#F5F5F5').fontColor(this.gridSize === (index + 2) ? '#FFFFFF' : '#000000').height(px2vp(200))}, (item: string) => item)}.width(px2vp(1800))}.margin(5).margin({ bottom: 20 }).width("100%").scrollable(ScrollDirection.Horizontal).height(px2vp(210))}build() {Column() {// 标题Text('专注力训练').fontSize(30).fontWeight(FontWeight.Bold).margin({ top: 20, bottom: 10 })// 计时器和最佳时间Row() {Text(`当前时间: ${this.timer}`).fontSize(18).margin({ right: 20 })Text(`最佳时间: ${this.bestTime}`).fontSize(18)}.margin({ bottom: 20 })// 网格大小选择this.ButtonListView()// 开始/重置按钮Button(this.isGameStarted ? '重置游戏' : '开始游戏').onClick(() => {if (this.isGameStarted) {this.resetGame();} else {this.startGame();}}).width('50%').margin({ bottom: 20 })// 游戏网格Grid() {ForEach(this.gridData, (number: number) => {GridItem() {Button(`${number}`).width('100%').height('100%').backgroundColor(this.isGameFinished? '#90EE90': number < this.currentNumber? '#90EE90': number === this.currentNumber? '#007DFF': '#F5F5F5').fontColor(number === this.currentNumber || number < this.currentNumber? '#FFFFFF': '#000000').onClick(() => this.handleCellClick(number)).enabled(this.isGameStarted && !this.isGameFinished)}})}.columnsTemplate(new Array(this.gridSize).fill('1fr').join(' ')).rowsTemplate(new Array(this.gridSize).fill('1fr').join(' ')).columnsGap(1).rowsGap(1).width('95%').height('60%').margin({ bottom: 20 })}.width('100%').height('100%').padding(15)}
}    
http://www.lryc.cn/news/588217.html

相关文章:

  • Java学习————————ThreadLocal
  • 九、官方人格提示词汇总(中-2)
  • 【笔记】chrome 无法打开特定协议或访问特定协议时卡死
  • 计算机基础:小端字节序
  • muduo面试准备
  • 算法:投票法
  • Debezium日常分享系列之:Debezium 3.2.0.Final发布
  • 观察应用宝进程的自启动行为
  • JAVA经典单例模式
  • 分布式系统中设计临时节点授权的自动化安全审计
  • 生信技能74 - WGS插入片段长度分布数据提取与绘图
  • Vue3 学习教程,从入门到精通,Vue 3 表单控件绑定详解与案例(7)
  • Linux连接跟踪Conntrack:原理、应用与内核实现
  • 分布式一致性协议
  • 零基础 “入坑” Java--- 十一、多态
  • 详解同步、异步、阻塞、非阻塞
  • 12.4 Hinton与Jeff Dean突破之作:稀疏门控MoE如何用1%计算量训练万亿参数模型?
  • UM680A模块接地与散热和封装推荐设计
  • MIPI DSI(三) MIPI DSI 物理层和 D-PHY
  • 2D和3D激光slam的点云去运动畸变
  • SLAM 前端
  • Doll靶机渗透
  • openEuler系统PCIE降速方法简介
  • 基于YOLOV8的烟火检测报警系统的设计与实现【全网独一、报警声音机制、实时画面、系统交互、日志记录】
  • SSM框架学习——day1
  • MySQL窗口函数详讲
  • VUE3 添加长按手势
  • Web 前端面试
  • C++-linux 7.文件IO(一)系统调用
  • Day34 Java方法05 可变参数