HarmonyOS 6.1 开发者盛宴|《灵犀厨房》【元服务】一键烹饪推荐原子化服务------免安装直达美味
摘要 :前面的篇章《HarmonyOS 6.1 开发者盛宴|灵犀厨房APP【通知系统】延时烹饪提醒------让通知不再错过关键步骤》中,我们已为《灵犀厨房》装上了通知系统------延时提醒 + WantAgent 回到应用。但这里有一个产品层面的断层:用户只是想"看看今天吃什么",却必须打开完整 App------加载登录、加载首页、等待推荐引擎计算。能不能更轻?本篇,我们将深入 HarmonyOS 6.1.0(API 23)的原子化服务(Atomic Service)能力,为《灵犀厨房》构建一个免安装的一键烹饪推荐元服务 :用户通过全局搜索即可直达"今天吃什么"推荐页,3 秒出结果,用完即走。元服务通过 HSP 共享包复用现有
RecommendEngine,零重复代码。严格遵循 API 23 规范。🔗 配套工程集成篇 :本篇侧重于元服务的 UI 设计与业务逻辑。元服务与主应用的工程整合(三模块架构、HSP 共享包搭建、Want 跳转参数桥梁)请阅读下一篇
《【元服务】三模块一体化工程集成------打通免安装跳转的最后一公里》。
一、引言与系列定位
经过前面多篇文章的积累,《灵犀厨房》已经是一个功能完备的应用:AI 推荐、语音播报、声控操作、健康分析、厨电模拟、分布式流转、通知提醒......但每次用户想用它的核心功能------"今天吃什么"------都需要:
| 步骤 | 耗时 | 用户心理 |
|---|---|---|
| 找到并点开灵犀厨房图标 | 2s | "我就想看看吃啥,至于吗" |
| 等登录页加载 → 自动跳转 | 3s | "快点......" |
| 首页推荐引擎计算 → 渲染 | 2s | "等的就是这几张卡片" |
| 总计 | ~7s | 焦虑感:7 秒在"午餐焦虑"中漫长得像 70 秒 |
而"今天吃什么"恰恰是最适合原子化服务的场景------需求单一、流程极短、用完即走。
设计决策:什么是原子化服务(Atomic Service)?
原子化服务是 HarmonyOS 独有的轻量级服务形态。它的核心特征:免安装、即点即用 。用户不需要下载完整 App、不需要注册登录------通过全局搜索、桌面卡片、扫码、NFC 碰一碰等方式即可直达服务页面。从技术角度,它是一个与主应用共享 bundleName 的独立模块,module.json5 中设置 installationFree: true 即可启用。
本篇的设计原则:
- 复用现有能力,零重复代码 :元服务通过 HSP 共享包(
shared模块)引用RecommendEngine,不重复实现推荐逻辑 - 极致轻量:元服务模块只有 1 个 Ability + 1 个页面 + 配置文件,总共约 3 个文件
- 3 秒体验:用户进入 → 推荐引擎计算 → 卡片展示,全链路 < 3 秒
📌 阅读提示:本篇与下一篇是"设计→落地"的配套关系。
- 本篇:回答"元服务怎么设计"------UI、推荐逻辑、用户交互
- 下一篇:回答"元服务怎么集成"------工程配置、模块划分、跨模块跳转
建议先阅读本篇理解元服务的业务逻辑,再阅读下一篇完成工程搭建。
二、核心原理与底层机制深度解读
2.1 原子化服务的三种分发路径
HarmonyOS 6.1.0(API 23)为原子化服务提供了三条用户触达路径:

图一解读 :用户通过三条路径触发元服务------全局搜索(输入"今天吃什么")、桌面卡片(固定在负一屏)、NFC 碰一碰(碰厨房标签)。系统 Atomic Service 框架识别 bundleName 和 moduleName,加载对应的元服务模块,最终由 AtomicServiceAbility 渲染推荐页面。
| 路径 | 触发方式 | 用户体验 | 适用场景 |
|---|---|---|---|
| A: 全局搜索 | 桌面下拉搜索"今天吃什么" | 关键词匹配 → 直达服务 | 日常使用最高频 |
| B: 桌面卡片 | 左滑负一屏添加卡片 | 固定在桌面,抬手即看 | 每日查看推荐 |
| C: NFC 碰一碰 | 手机碰厨房 NFC 标签 | 物理世界触发数字服务 | 智能厨房场景 |
为什么先从全局搜索入手?
全局搜索是 HarmonyOS 原子化服务最成熟的发现通道。开发者只需在元服务模块中声明 skills,系统会自动索引页面内容。用户搜索"今天吃什么""晚餐推荐""灵犀厨房"等关键词时,元服务会出现在搜索结果中------点击即跳转,无需安装。
2.2 原子化服务 vs 传统 App:一图看懂差异

图二解读 :右侧传统应用需要下载、安装、登录、加载首页四步才能看到推荐,左侧原子化服务只需搜索触发一步直达。核心差异在于免安装 和免登录------系统预加载机制让元服务的冷启动速度远超传统应用。
| 维度 | 传统应用 | 原子化服务 |
|---|---|---|
| 安装容量 | 50MB+(含所有资源) | 0MB(免安装) |
| 首次启动 | 3-5秒(冷启动) | <1秒(系统预加载) |
| 用户门槛 | 下载→安装→注册→登录 | 搜索即达 |
| 功能范围 | 全部功能 | 单一场景(推荐+点击看详情) |
| 代码复用 | --- | 共享主应用的 bundleName 和业务模块 |
2.3 HSP 共享包设计原理:元服务如何复用推荐引擎
这是本篇最核心的设计------元服务如何复用主应用的业务逻辑。根据华为官方文档《元服务与应用可复用设计》,单团队 + 元服务分包 场景下,推荐使用 HSP(Harmony Shared Package)共享包作为代码复用方案:

图三解读 :将 RecommendEngine、Recipe 模型、MockData 等可复用代码从 entry 模块迁移到独立的 shared HSP 模块。entry 和 atomicservice 通过各自的 oh-package.json5 声明对 shared 的依赖------编译时 hvigor 自动解析依赖关系,构建产物中 shared 模块只打包一次,两个消费者共享引用。
📌 HSP 共享包的实际搭建过程(创建 shared 模块、配置 oh-package.json5、声明依赖)已记录在下一篇《工程集成篇》的 Step 2-4 中,含完整的配置文件代码。本篇仅阐述设计原理,不再重复配置代码。
| 维度 | 跨模块直接 import | HSP 共享包 |
|---|---|---|
| 路径稳定性 | 依赖文件系统相对路径,模块移动即失效 | 通过 oh-package.json5 声明依赖,路径稳定 |
| 编译产物 | 两份完整副本(各自打包) | 单份共享产物,两个模块引用同一份 |
| 官方推荐度 | 非标准用法 | ✅ 官方推荐方案 |
三、架构设计 / 核心逻辑图解
3.1 元服务模块的四层架构

图四解读 :元服务 UI 层(绿色区)只有 1 个 Ability + 1 个页面,通过 import from 'shared' 引用共享的推荐引擎和数据模型。主应用的首页推荐和菜谱详情页同样引用 shared 模块。注意元服务模块不包含任何业务逻辑------所有算法都在 shared 中,元服务只负责 UI 渲染和用户交互。
设计原则:
- HSP 集中共享 :
RecommendEngine、Recipe、MockData、UserPreference均位于sharedHSP 模块 - UI 轻量化:元服务模块只放 UI 代码(1 个 Ability + 1 个页面),不含业务逻辑
- 独立入口 :元服务有自己的
AtomicServiceAbility,不依赖主应用的EntryAbility - 点击跳转 :推荐卡片点击后通过
Want拉起主应用的菜谱详情页
3.2 完整时序图:从搜索到详情页

图五解读 :用户从全局搜索"今天吃什么"到看到菜谱详情,经历了六个阶段:(1)系统匹配元服务索引并加载模块;(2)元服务调用推荐引擎计算推荐;(3)渲染 4 张菜谱卡片;(4)用户点击卡片;(5)元服务通过 Want 拉起主应用;(6)主应用显示详情页。整个链路的核心优势在于免安装------前三步不需要下载完整应用。
四、实战:搭建一键烹饪推荐原子化服务
场景确认 :按照华为官方《元服务与应用可复用设计》指南,本系列属于单团队、元服务分包 + 应用支持 场景。主应用(entry)和元服务(atomicservice)通过
HSP 共享包(shared 模块)复用推荐引擎和数据模型。
Step 1:元服务模块目录结构
元服务模块的内部结构如下(完整的工程目录见下一篇《工程集成篇》):
atomicservice/ ← ★ 新增元服务模块
├── src/main/
│ ├── ets/
│ │ ├── entryability/
│ │ │ └── AtomicServiceAbility.ets
│ │ └── pages/
│ │ └── Index.ets
│ ├── module.json5
│ └── resources/
├── build-profile.json5
├── hvigorfile.ts
└── oh-package.json5
说明 :元服务通过 HSP 共享包(
shared模块)复用RecommendEngine、Recipe模型、MockData。HSP 共享包的创建与配置详见下一篇《工程集成篇》的 Step 2-3。
Step 2:实现 AtomicServiceAbility ------ 入口 Ability
这是元服务的唯一入口 ,负责加载推荐页面。与主应用的 EntryAbility 不同,元服务的入口 Ability 极简------不需要初始化通知渠道、不需要权限请求、不需要登录状态检查:
typescript
// atomicservice/src/main/ets/entryability/AtomicServiceAbility.ets
// 所属层:原子化服务入口
// 职责:Atomic Service 的 UIAbility 入口
// API Level: 23
import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
import { window } from '@kit.ArkUI';
const DOMAIN = 0x0001;
const TAG = 'AtomicServiceAbility';
export default class AtomicServiceAbility extends UIAbility {
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
hilog.debug(DOMAIN, TAG, 'AtomicServiceAbility onCreate');
// 元服务不需要复杂初始化------无登录、无持久化
// 唯一的事:设置颜色模式跟随系统
try {
this.context.getApplicationContext().setColorMode(0);
} catch (err) {
hilog.warn(DOMAIN, TAG, '设置颜色模式失败: %{public}s', JSON.stringify(err));
}
}
onWindowStageCreate(windowStage: window.WindowStage): void {
hilog.debug(DOMAIN, TAG, 'AtomicServiceAbility onWindowStageCreate');
// ★ 加载推荐页(唯一的页面)
windowStage.loadContent('pages/Index', (err) => {
if (err.code) {
hilog.error(DOMAIN, TAG,
'元服务页面加载失败, code: %{public}d, msg: %{public}s',
err.code, err.message);
return;
}
hilog.debug(DOMAIN, TAG, '元服务推荐页加载成功');
});
}
onDestroy(): void {
hilog.debug(DOMAIN, TAG, 'AtomicServiceAbility onDestroy');
}
}
核心点解读 :
onCreate中只做了一件事------设置颜色模式跟随系统。没有初始化通知渠道(元服务不支持通知)、没有权限请求、没有登录状态检查。onWindowStageCreate中直接加载推荐页------这是元服务唯一需要渲染的页面。
Step 3:构建推荐页面 Index.ets ------ 「一键推荐」核心 UI
这是元服务的唯一页面,用户体验的最终落地处:
typescript
// atomicservice/src/main/ets/pages/Index.ets
// 所属层:原子化服务 UI 层
// 职责:一键烹饪推荐页面
// API Level: 23
// 依赖: shared HSP 模块(RecommendEngine, Recipe, UserPreference)
import { common, Want } from '@kit.AbilityKit';
// ★ HSP 共享包导入 ------ 通过 shared 模块名引用,不依赖文件路径
import { Recipe } from 'shared';
import { recommendEngine } from 'shared';
import { UserPreference } from 'shared';
@Entry
@ComponentV2
struct AtomicRecommendPage {
@Local recommendations: Recipe[] = [];
@Local isLoading: boolean = true;
@Local errorMsg: string = '';
aboutToAppear(): void {
this.loadRecommendations();
}
/**
* 一键加载推荐
* 复用主应用的 recommendEngine,传入默认偏好
*/
private loadRecommendations(): void {
this.isLoading = true;
this.errorMsg = '';
try {
// 使用默认偏好(用户未登录时,推荐引擎返回通用推荐)
const defaultPref: UserPreference = {
favoriteTags: [], // 无偏好标签 → 所有菜谱参与评分
allergies: [], // 无忌口
maxCalories: 0 // 0 表示不限卡路里
};
// ★ 调用共享的推荐引擎
this.recommendations = recommendEngine.getRecommendations(
defaultPref, [], 4
);
this.isLoading = false;
} catch (err) {
this.errorMsg = '推荐加载失败,请下拉刷新';
this.isLoading = false;
console.error(`[AtomicRecommend] 推荐失败: ${JSON.stringify(err)}`);
}
}
/**
* 点击菜谱卡片 → 通过 Want 拉起主应用的 RecipeDetailPage
*/
private handleRecipeClick(recipe: Recipe): void {
try {
const ctx = this.getUIContext().getHostContext() as common.UIAbilityContext;
const want: Want = {
bundleName: 'com.annan.lingxikitchen',
abilityName: 'EntryAbility',
parameters: {
recipeId: recipe.id,
recipeName: recipe.name,
recipeIngredients: recipe.ingredients
}
};
ctx.startAbility(want);
console.log(`[AtomicRecommend] 跳转主应用 → ${recipe.name}`);
} catch (err) {
console.error(`[AtomicRecommend] 跳转主应用失败: ${JSON.stringify(err)}`);
}
}
build() {
Column() {
// ── 顶部标题区 ──
Row() {
Text('🍳').fontSize(32)
Column({ space: 2 }) {
Text('今天吃什么?')
.fontSize(22).fontWeight(FontWeight.Bold).fontColor('#333')
Text('灵犀厨房 · 一键推荐')
.fontSize(12).fontColor('#999')
}
.margin({ left: 12 })
.alignItems(HorizontalAlign.Start)
Blank()
// 刷新按钮
Button({ type: ButtonType.Circle }) {
SymbolGlyph($r('sys.symbol.arrow_clockwise'))
.fontSize(20).fontColor(['#FF6B35'])
}
.width(40).height(40).backgroundColor('#FFF0E6')
.onClick(() => this.loadRecommendations())
}
.width('100%')
.padding({ left: 20, right: 20, top: 16, bottom: 12 })
// ── 分割线 ──
Divider().color('#F0F0F0').width('90%')
// ── 内容区:三态渲染 ──
if (this.isLoading) {
// 加载中
Column({ space: 12 }) {
LoadingProgress().width(40).height(40).color('#FF6B35')
Text('正在为你推荐...').fontSize(14).fontColor('#999')
}
.width('100%').layoutWeight(1).justifyContent(FlexAlign.Center)
} else if (this.errorMsg.length > 0) {
// 错误状态
Column({ space: 12 }) {
Text('😅').fontSize(40)
Text(this.errorMsg).fontSize(14).fontColor('#999')
Button('重新加载')
.fontSize(14).height(36)
.backgroundColor('#FF6B35').fontColor(Color.White)
.borderRadius(18)
.onClick(() => this.loadRecommendations())
}
.width('100%').layoutWeight(1).justifyContent(FlexAlign.Center)
} else {
// 推荐卡片列表
List({ space: 12 }) {
ForEach(this.recommendations, (recipe: Recipe, index: number) => {
ListItem() {
this.buildRecipeCard(recipe, index)
}
.onClick(() => this.handleRecipeClick(recipe))
}, (recipe: Recipe) => recipe.id.toString())
}
.width('100%').layoutWeight(1)
.padding({ left: 16, right: 16 })
}
}
.width('100%').height('100%')
.backgroundColor('#FFF8F0')
}
/**
* 菜谱卡片 @Builder
*/
@Builder
buildRecipeCard(recipe: Recipe, index: number) {
Row({ space: 12 }) {
// 序号
Text(`${index + 1}`)
.fontSize(20).fontWeight(FontWeight.Bold)
.fontColor('#FF6B35')
.width(32).height(32).borderRadius(16)
.backgroundColor('#FFF0E6')
.textAlign(TextAlign.Center)
// 菜谱信息
Column({ space: 4 }) {
Text(recipe.name)
.fontSize(16).fontWeight(FontWeight.Medium).fontColor('#333')
.maxLines(1).textOverflow({ overflow: TextOverflow.Ellipsis })
Row({ space: 8 }) {
Text(`${recipe.calories} kcal`)
.fontSize(11).fontColor('#FF6B35')
.padding({ left: 6, right: 6, top: 2, bottom: 2 })
.backgroundColor('#FFF0E6').borderRadius(4)
if (recipe.tags && recipe.tags.length > 0) {
Text(recipe.tags.slice(0, 2).join(' · '))
.fontSize(11).fontColor('#999')
.maxLines(1).textOverflow({ overflow: TextOverflow.Ellipsis })
}
}
Text(recipe.ingredients.slice(0, 3).join('、'))
.fontSize(11).fontColor('#999')
.maxLines(1).textOverflow({ overflow: TextOverflow.Ellipsis })
}
.alignItems(HorizontalAlign.Start)
.layoutWeight(1)
// 箭头
SymbolGlyph($r('sys.symbol.chevron_right'))
.fontSize(16).fontColor(['#CCC'])
}
.width('100%').padding(14)
.backgroundColor(Color.White).borderRadius(12)
.shadow({ radius: 4, color: '#10000000', offsetY: 2 })
}
}
核心点解读:
- HSP 导入 :
import { recommendEngine } from 'shared'------通过oh-package.json5声明的依赖名引用共享模块,路径简洁且不依赖文件系统层级- Want 拉起主应用 :
handleRecipeClick中构造Want对象,指定bundleName: 'com.annan.lingxikitchen'和abilityName: 'EntryAbility'。系统收到此 Want 后,如果主应用已安装则直接跳转,如果未安装则引导下载- 默认偏好 :元服务不依赖用户登录,使用空偏好调用
recommendEngine.getRecommendations()。推荐引擎在偏好为空时返回通用高质量菜谱- 三态渲染:加载中(LoadingProgress + 提示文字)、错误状态(Emoji + 错误信息 + 重试按钮)、结果列表(4 张菜谱卡片),覆盖全部 UI 状态
- 刷新机制:顶部刷新按钮让用户重新计算推荐,去重历史自动避免重复推荐
四、代码增删改清单
| 文件 | 新增/修改 | 职责 |
|---|---|---|
atomicservice/src/main/ets/entryability/AtomicServiceAbility.ets |
新增 | 元服务入口 Ability,极简初始化 + 加载推荐页 |
atomicservice/src/main/ets/pages/Index.ets |
新增 | 一键推荐 UI:三态渲染 + Want 跳转 + HSP 导入 |
atomicservice/src/main/resources/base/profile/main_pages.json |
新增 | 页面路由声明 |
atomicservice/src/main/resources/base/element/string.json |
新增 | 模块描述、Ability 标签等字符串资源 |
atomicservice/build-profile.json5 |
新增 | 声明 atomicService 编译类型 |
atomicservice/hvigorfile.ts |
新增 | 模块编译入口 |
atomicservice/oh-package.json5 |
新增 | 声明 shared HSP 依赖 |
📌 以下配置文件的完整代码见下一篇《工程集成篇》:
atomicservice/src/main/module.json5(type: "feature"、installationFree: true、skills搜索索引)shared/Index.ets(HSP 导出入口)shared/oh-package.json5(HSP 模块声明)entry/oh-package.json5(新增 shared 依赖)- 根目录
build-profile.json5(modules 数组新增 shared + atomicservice)
五、设计决策
| 决策 | 选择 | 理由 |
|---|---|---|
| 元服务入口 | 单一 Ability + 单一页面 | 原子化服务的核心价值是"轻",一个推荐场景不需要多页面路由 |
| 代码复用 | HSP 共享包(shared 模块) |
按官方指南单团队场景推荐,编译产物收敛,路径不依赖文件系统 |
| 用户偏好 | 元服务使用空偏好(默认推荐) | 免登录场景下无法获取用户偏好数据,空偏好 + 季节加权保证推荐质量 |
| 点击跳转 | Want.startAbility 拉起主应用 |
元服务只负责"推荐",不负责"详情"。点击后由主应用接力 |
| 刷新机制 | 顶部刷新按钮 | 原子化服务不支持下拉刷新手势(与系统导航手势冲突) |
| 卡片数量 | 4 道菜 | 与主应用首页推荐保持一致,移动端一屏可见 3-4 张卡片 |
六、运行与结果验证
6.1 前置条件
- 已完成三模块工程搭建(见下一篇《工程集成篇》)
- 使用自动签名或手动签名(元服务签名配置与主应用相同)
6.2 操作步骤
- DevEco Studio 中 Run atomicservice 模块到模拟器或真机
- 元服务加载推荐页 → 显示 4 道推荐菜谱卡片

- 点击刷新按钮 → 重新计算推荐,卡片内容可能变化

- 点击某道菜 → 系统拉起主应用 → 跳转到 RecipeDetailPage

6.3 预期日志
[AtomicServiceAbility] AtomicServiceAbility onCreate
[AtomicServiceAbility] AtomicServiceAbility onWindowStageCreate
[AtomicServiceAbility] 元服务推荐页加载成功
[RecommendEngine] 偏好: , 食材: , 季节: 春季
[RecommendEngine] 推荐结果: 红烧肉、清蒸鲈鱼、番茄炒蛋、酸辣汤
[AtomicRecommend] 跳转主应用 → 红烧肉
七、注意事项
7.1 原子化服务的冷启动优化
- 元服务模块不引入任何不必要的 Kit(如
@kit.NotificationKit、@kit.HealthKit) - 页面只渲染首屏可见内容,不做预加载
AtomicServiceAbility.onCreate中不做任何网络请求或文件 I/O
7.2 原子化服务不支持的 API
在 API 23 中,原子化服务模块不支持以下能力:
@kit.NotificationKit(通知发送)@kit.BackgroundTasksKit(后台任务)ServiceExtensionAbility(后台服务)DataShareExtensionAbility(数据共享)
如果需要通知能力,应引导用户下载完整应用。
7.3 Want 拉起主应用的 fallback
如果用户设备上未安装主应用,startAbility(want) 会失败。生产环境中应使用 startAbility(want, {}) 的二参数版本,或通过 @kit.AbilityKit 的 openLink 跳转到 AppGallery 引导下载。
八、本阶段总结与下篇预告
今天,我们为《灵犀厨房》构建了一键烹饪推荐原子化服务------实现了"免安装、即点即用"的极致轻量体验:
- HSP 共享包架构 :遵循华为官方指南,
sharedHSP 模块集中管理推荐引擎和数据模型,entry 和 atomicservice 共享引用 - 极致精简 :
AtomicServiceAbility不做任何复杂初始化,onCreate只设颜色模式,onWindowStageCreate只加载页面 - 3 秒体验:用户搜索 → 系统加载元服务 → 推荐引擎计算 → 卡片展示,全链路 < 3 秒
- Want 无缝衔接 :点击菜谱卡片通过
Want拉起主应用,元服务"种草"、主应用"成交"
但元服务要和主应用真正打通,还需要解决工程配置、模块划分、参数传递等一系列问题------这就是下一篇的主题。
下篇预告 :下一篇 《【元服务】三模块一体化工程集成------打通免安装跳转的最后一公里》 。我们将把元服务模块与主应用整合到同一工程中,完成 HSP 共享包搭建、EntryAbility 路由改造、RecipeBridge 参数桥梁实现,覆盖冷启动/热启动/后台唤起全部场景。
📚 本系列持续更新中:下一篇将完成元服务的工程集成,打通 Want 跳转参数桥梁。
🔗 专栏入口 :《HarmonyOS6.1全场景实战》合集
📦 获取基线版本源码包 :包括第1-15篇代码 + 架构文档 + Flask 后端如果你觉得这篇文章对你有帮助,请不要吝啬你的点赞 👍、收藏 ⭐ 和评论 💬。你的支持,是我继续输出高质量技术内容的全部动力。
纯血鸿蒙,免安装直达美味。我们下一篇见!