HarmonyOS应用<趣答>开发第41篇:原子化服务——HarmonyOS 7新特性深度解析

📖 引言
随着HarmonyOS 7的发布,原子化服务成为了核心亮点之一。原子化服务是一种新型的应用形态,它打破了传统应用的安装和使用模式,让用户可以"即点即用",无需完整安装即可体验应用功能。
在《趣答》项目中,我们将探索如何利用原子化服务打造轻量化的学习体验,让用户能够快速访问每日挑战、错题本等核心功能。
通过本文,你将掌握:
- 原子化服务的概念和优势
- 在HarmonyOS 7中创建原子化服务的完整流程
- 如何为《趣答》应用添加原子化服务入口
- 原子化服务的生命周期管理
🎯 学习目标
完成本文后,你将能够:
- ✅ 理解原子化服务的核心概念和设计理念
- ✅ 创建和配置原子化服务的Module
- ✅ 实现原子化服务的UI界面和交互逻辑
- ✅ 配置服务卡片和快捷入口
- ✅ 处理原子化服务的生命周期事件
💡 需求分析
功能模块设计
| 模块 | 功能描述 | 技术要点 |
|---|---|---|
| 原子化服务入口 | 提供每日挑战的快捷访问 | ServiceAbility配置、路由跳转 |
| 服务卡片 | 展示今日挑战进度和积分 | Widget卡片、数据绑定 |
| 轻量化页面 | 简化版的答题界面 | 独立UIAbility、状态管理 |
| 数据同步 | 与主应用共享用户数据 | 分布式数据管理、Preferences |
🛠️ 核心实现
步骤1: 创建原子化服务Module
功能说明
在HarmonyOS 7中,原子化服务通过独立的Module实现。我们需要创建一个新的Module来承载原子化服务的功能。
完整代码
// build-profile.json5 (原子化服务Module配置)
{
"apiType": "stageMode",
"buildOption": {
"product": "default",
"arkOptions": {
"sourceType": "ets",
"runtimeOS": "HarmonyOS",
"apiVersion": 26
}
},
"modules": [
{
"name": "service_entry",
"srcPath": "./service_entry",
"target": "entry",
"buildOption": {
"buildMode": "release",
"hivigor": {
"toolchain": "ohos"
}
}
}
]
}
// service_entry/oh-package.json5
{
"name": "@ohos/service_entry",
"version": "1.0.0",
"description": "原子化服务入口Module",
"main": "",
"dependencies": {},
"devDependencies": {}
}
代码解析
1. Module类型选择
"apiType": "stageMode"
原理/说明:
- HarmonyOS 7推荐使用Stage模型
- 原子化服务必须使用Stage模型
- apiVersion设置为26,即HarmonyOS 7
2. Module配置
{
"name": "service_entry",
"srcPath": "./service_entry",
"target": "entry"
}
原理/说明:
name定义Module名称srcPath指定Module源码路径target设置为"entry"表示这是入口Module
步骤2: 配置module.json5
功能说明
module.json5是原子化服务的核心配置文件,用于声明服务的能力、路由和权限。
完整代码
// service_entry/src/main/module.json5
{
"module": {
"name": "service_entry",
"type": "service",
"deviceTypes": [
"phone",
"tablet",
"car",
"wearable"
],
"deliveryWithInstall": true,
"installationFree": true,
"abilities": [
{
"name": "DailyChallengeService",
"type": "service",
"srcEntry": "./ets/entryability/DailyChallengeEntryAbility.ets",
"description": "$string:daily_challenge_service_desc",
"icon": "$media:icon",
"label": "$string:daily_challenge_label",
"startWindowIcon": "$media:icon",
"startWindowBackground": "$color:start_window_background",
"visible": true,
"exported": true,
"skills": [
{
"entities": [
"entity.system.home",
"entity.system.dock"
],
"actions": [
"action.system.home"
]
}
],
"extensions": {
"form": {
"forms": [
{
"name": "DailyChallengeForm",
"description": "$string:daily_challenge_form_desc",
"src": "./ets/forms/DailyChallengeForm.ets",
"uiSyntax": "arkts",
"window": {
"designWidth": 720,
"autoDesignWidth": true
},
"type": "reactive",
"isDefault": true,
"updateEnabled": true,
"scheduledUpdateTime": "10:00",
"updateDuration": 1440,
"defaultDimension": "2x2",
"supportDimensions": [
"1x1",
"2x2",
"2x4"
]
}
]
}
}
}
],
"requestPermissions": [
{
"name": "ohos.permission.INTERNET"
},
{
"name": "ohos.permission.DISTRIBUTED_DATASYNC"
}
]
}
}
代码解析
1. Module类型配置
"type": "service",
"deliveryWithInstall": true,
"installationFree": true
原理/说明:
type: "service"表示这是一个原子化服务ModuledeliveryWithInstall: true表示随应用安装一起交付installationFree: true支持免安装使用
2. ServiceAbility配置
{
"name": "DailyChallengeService",
"type": "service",
"visible": true,
"exported": true
}
原理/说明:
type: "service"表示这是一个Service类型的Abilityvisible: true使服务在系统可见exported: true允许其他应用调用
3. 服务卡片配置
"extensions": {
"form": {
"forms": [
{
"name": "DailyChallengeForm",
"type": "reactive",
"updateEnabled": true,
"defaultDimension": "2x2"
}
]
}
}
原理/说明:
type: "reactive"表示卡片数据可以响应式更新updateEnabled: true允许定时更新defaultDimension: "2x2"设置默认卡片尺寸
步骤3: 创建EntryAbility
功能说明
EntryAbility是原子化服务的入口,负责初始化服务、处理生命周期事件。
完整代码
// service_entry/src/main/ets/entryability/DailyChallengeEntryAbility.ets
import { UIAbility, AbilityStage } from '@ohos.ability';
import { hilog } from '@ohos.hilog';
import { PreferencesUtil } from '../utils/PreferencesUtil';
const TAG = 'DailyChallengeEntryAbility';
export default class DailyChallengeEntryAbility extends UIAbility {
private preferencesUtil: PreferencesUtil | null = null;
onCreate(want, launchParam): void {
hilog.info(0x0000, TAG, 'DailyChallengeEntryAbility onCreate');
this.preferencesUtil = new PreferencesUtil(this.context);
this.preferencesUtil.init();
}
onDestroy(): void {
hilog.info(0x0000, TAG, 'DailyChallengeEntryAbility onDestroy');
if (this.preferencesUtil) {
this.preferencesUtil.destroy();
}
}
onForeground(): void {
hilog.info(0x0000, TAG, 'DailyChallengeEntryAbility onForeground');
}
onBackground(): void {
hilog.info(0x0000, TAG, 'DailyChallengeEntryAbility onBackground');
}
onWindowStageCreate(windowStage): void {
hilog.info(0x0000, TAG, 'DailyChallengeEntryAbility onWindowStageCreate');
windowStage.loadContent('pages/DailyChallengeService', (err, data) => {
if (err.code) {
hilog.error(0x0000, TAG, 'Failed to load content. Cause: %{public}s', JSON.stringify(err));
return;
}
hilog.info(0x0000, TAG, 'Succeeded in loading content. Data: %{public}s', JSON.stringify(data));
});
}
onWindowStageDestroy(): void {
hilog.info(0x0000, TAG, 'DailyChallengeEntryAbility onWindowStageDestroy');
}
}
代码解析
1. 生命周期管理
onCreate(want, launchParam): void {
this.preferencesUtil = new PreferencesUtil(this.context);
this.preferencesUtil.init();
}
onDestroy(): void {
if (this.preferencesUtil) {
this.preferencesUtil.destroy();
}
}
原理/说明:
onCreate在服务创建时调用,初始化数据存储onDestroy在服务销毁时调用,释放资源- 原子化服务的生命周期与普通UIAbility类似
2. 页面加载
windowStage.loadContent('pages/DailyChallengeService', (err, data) => {
if (err.code) {
hilog.error(0x0000, TAG, 'Failed to load content');
return;
}
});
原理/说明:
- 使用
loadContent加载原子化服务的页面 - 页面路径相对于当前Module的pages目录
步骤4: 创建服务卡片组件
功能说明
服务卡片是原子化服务的重要组成部分,用户可以在桌面直接看到服务内容。
完整代码
// service_entry/src/main/ets/forms/DailyChallengeForm.ets
import { formBindingData } from '@ohos.form';
import { hilog } from '@ohos.hilog';
const TAG = 'DailyChallengeForm';
@Entry
@Component
struct DailyChallengeForm {
@State currentScore: number = 0;
@State todayProgress: number = 0;
@State totalQuestions: number = 5;
build() {
Column() {
Row() {
Image($r('app.media.icon'))
.width(40)
.height(40)
.borderRadius(8)
Column() {
Text('每日挑战')
.fontSize(18)
.fontWeight(FontWeight.Bold)
.fontColor('#333333')
Text(`已完成 ${this.todayProgress}/${this.totalQuestions} 题`)
.fontSize(12)
.fontColor('#999999')
}
.margin({ left: 12 })
}
.width('100%')
.padding({ left: 16, right: 16, top: 16 })
Progress({ value: this.todayProgress, total: this.totalQuestions, type: ProgressType.Ring })
.width(60)
.height(60)
.color('#007DFF')
.backgroundColor('#EEEEEE')
.margin({ top: 12 })
Text(`积分: ${this.currentScore}`)
.fontSize(14)
.fontColor('#007DFF')
.fontWeight(FontWeight.Medium)
.margin({ top: 8, bottom: 16 })
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
.backgroundColor('#FFFFFF')
}
}
export const onFormEvent = (event: string, formId: string): void => {
hilog.info(0x0000, TAG, `Form event received: ${event}, formId: ${formId}`);
};
export const onFormUpdate = (formId: string): void => {
hilog.info(0x0000, TAG, `Form update triggered: ${formId}`);
};
export const provideFormData = (): formBindingData.FormBindingData => {
const dataObj: Record<string, Object> = {
'currentScore': 0,
'todayProgress': 0,
'totalQuestions': 5
};
return formBindingData.createFormBindingData(dataObj);
};
代码解析
1. 卡片布局
Column() {
Row() { /* 标题区域 */ }
Progress({ type: ProgressType.Ring }) { /* 进度环 */ }
Text(`积分: ${this.currentScore}`) { /* 积分显示 */ }
}
原理/说明:
- 使用Column垂直布局
- 包含标题、进度环和积分三个部分
- 响应式状态管理
2. 卡片生命周期函数
export const onFormEvent = (event: string, formId: string): void => { ... };
export const onFormUpdate = (formId: string): void => { ... };
export const provideFormData = (): formBindingData.FormBindingData => { ... };
原理/说明:
onFormEvent处理卡片事件onFormUpdate处理卡片更新provideFormData提供初始数据
步骤5: 创建轻量化服务页面
功能说明
原子化服务需要提供一个简化版的页面,让用户能够快速完成核心操作。
完整代码
// service_entry/src/main/ets/pages/DailyChallengeService.ets
import router from '@ohos.router';
import { QuizService } from '../services/QuizService';
import { UserService } from '../services/UserService';
@Entry
@Component
struct DailyChallengeService {
@State currentQuestion: any = null;
@State selectedAnswer: number = -1;
@State questionIndex: number = 0;
@State totalQuestions: number = 5;
@State score: number = 0;
@State isFinished: boolean = false;
private quizService: QuizService = new QuizService();
private userService: UserService = new UserService();
build() {
Column() {
if (!this.isFinished) {
this.buildQuizSection();
} else {
this.buildResultSection();
}
}
.width('100%')
.height('100%')
.padding(20)
.backgroundColor('#F5F5F5')
.onAppear(() => {
this.loadDailyChallenge();
})
}
buildQuizSection() {
return Column() {
Row() {
Text(`第 ${this.questionIndex + 1}/${this.totalQuestions} 题`)
.fontSize(16)
.fontColor('#666666')
.margin({ bottom: 20 })
}
Text(this.currentQuestion?.question || '')
.fontSize(20)
.fontWeight(FontWeight.Bold)
.fontColor('#333333')
.margin({ bottom: 30 })
.textAlign(TextAlign.Center)
Column() {
ForEach(this.currentQuestion?.options || [], (option: string, index: number) => {
Button(option)
.width('100%')
.height(56)
.margin({ bottom: 12 })
.borderRadius(12)
.backgroundColor(this.selectedAnswer === index ? '#007DFF' : '#FFFFFF')
.fontColor(this.selectedAnswer === index ? '#FFFFFF' : '#333333')
.onClick(() => {
this.selectedAnswer = index;
})
})
}
Button('确认答案')
.width('100%')
.height(56)
.margin({ top: 30 })
.borderRadius(12)
.backgroundColor('#007DFF')
.fontColor('#FFFFFF')
.enabled(this.selectedAnswer !== -1)
.onClick(() => {
this.submitAnswer();
})
}
}
buildResultSection() {
return Column() {
Text('挑战完成!')
.fontSize(28)
.fontWeight(FontWeight.Bold)
.fontColor('#333333')
.margin({ bottom: 20 })
Text(`本次得分: ${this.score}分`)
.fontSize(24)
.fontColor('#007DFF')
.margin({ bottom: 30 })
Row() {
Button('返回')
.width(150)
.height(50)
.borderRadius(12)
.backgroundColor('#FFFFFF')
.fontColor('#333333')
.onClick(() => {
router.back();
})
Button('打开完整应用')
.width(150)
.height(50)
.margin({ left: 20 })
.borderRadius(12)
.backgroundColor('#007DFF')
.fontColor('#FFFFFF')
.onClick(() => {
this.openMainApp();
})
}
}
}
async loadDailyChallenge() {
this.currentQuestion = await this.quizService.getDailyQuestion(this.questionIndex);
}
async submitAnswer() {
const isCorrect = await this.quizService.checkAnswer(
this.currentQuestion.id,
this.selectedAnswer
);
if (isCorrect) {
this.score += 20;
}
this.questionIndex++;
if (this.questionIndex >= this.totalQuestions) {
this.isFinished = true;
await this.userService.updateScore(this.score);
} else {
this.currentQuestion = await this.quizService.getDailyQuestion(this.questionIndex);
this.selectedAnswer = -1;
}
}
openMainApp() {
router.pushUrl({
url: 'pages/Index',
params: {
fromService: true
}
}, router.RouterMode.Single);
}
}
代码解析
1. 状态管理
@State currentQuestion: any = null;
@State selectedAnswer: number = -1;
@State questionIndex: number = 0;
@State isFinished: boolean = false;
原理/说明:
- 使用@State管理组件状态
currentQuestion存储当前题目isFinished控制答题/结果界面切换
2. 答题逻辑
async submitAnswer() {
const isCorrect = await this.quizService.checkAnswer(...);
if (isCorrect) {
this.score += 20;
}
this.questionIndex++;
}
原理/说明:
- 调用QuizService验证答案
- 正确答案加20分
- 切换到下一题或显示结果
3. 打开主应用
openMainApp() {
router.pushUrl({
url: 'pages/Index',
params: { fromService: true }
}, router.RouterMode.Single);
}
原理/说明:
- 使用router跳转到主应用首页
- 通过params传递来源标记
步骤6: 数据共享工具类
功能说明
原子化服务需要与主应用共享用户数据,需要创建数据同步工具。
完整代码
// service_entry/src/main/ets/utils/PreferencesUtil.ts
import { preferences } from '@ohos.data.preferences';
import { hilog } from '@ohos.hilog';
const TAG = 'PreferencesUtil';
const PREFERENCES_NAME = 'user_preferences';
export class PreferencesUtil {
private pref: preferences.Preferences | null = null;
private context: any = null;
constructor(context: any) {
this.context = context;
}
async init(): Promise<void> {
try {
this.pref = await preferences.getPreferences(this.context, PREFERENCES_NAME);
hilog.info(0x0000, TAG, 'Preferences initialized successfully');
} catch (err) {
hilog.error(0x0000, TAG, 'Failed to initialize preferences: %{public}s', JSON.stringify(err));
}
}
async getValue<T>(key: string, defaultValue: T): Promise<T> {
if (!this.pref) {
await this.init();
}
try {
return await this.pref!.get(key, defaultValue) as T;
} catch (err) {
hilog.error(0x0000, TAG, 'Failed to get value: %{public}s', JSON.stringify(err));
return defaultValue;
}
}
async setValue<T>(key: string, value: T): Promise<void> {
if (!this.pref) {
await this.init();
}
try {
await this.pref!.put(key, value);
await this.pref!.flush();
hilog.info(0x0000, TAG, 'Value set successfully: %{public}s', key);
} catch (err) {
hilog.error(0x0000, TAG, 'Failed to set value: %{public}s', JSON.stringify(err));
}
}
destroy(): void {
if (this.pref) {
preferences.deletePreferences(this.context, PREFERENCES_NAME);
}
}
}
代码解析
1. Preferences初始化
async init(): Promise<void> {
this.pref = await preferences.getPreferences(this.context, PREFERENCES_NAME);
}
原理/说明:
- 使用系统Preferences API存储数据
- 主应用和原子化服务使用相同的preferences名称实现数据共享
2. 数据读写
async getValue<T>(key: string, defaultValue: T): Promise<T> { ... }
async setValue<T>(key: string, value: T): Promise<void> { ... }
原理/说明:
- 泛型方法支持多种数据类型
setValue后调用flush()确保数据持久化
⚠️ 常见问题与解决方案
问题1: 原子化服务无法启动
现象:
原子化服务在桌面点击后没有反应,控制台报错"Ability not found"。
原因:
- Module配置错误
- service类型配置不正确
错误代码:
// ❌ 错误: type应该是"service"而不是"entry"
{
"type": "entry"
}
正确代码:
// ✅ 正确: 原子化服务Module类型应为"service"
{
"type": "service"
}
规则/建议:
- 原子化服务的Module类型必须是"service"
- 检查deviceTypes配置是否包含目标设备
- 确保srcEntry路径正确
问题2: 服务卡片不显示
现象:
桌面添加卡片后显示空白或加载失败。
原因:
- 卡片组件路径配置错误
- 卡片尺寸不支持
错误代码:
// ❌ 错误: src路径错误
{
"src": "./forms/DailyChallengeForm"
}
正确代码:
// ✅ 正确: 需要完整路径和文件扩展名
{
"src": "./ets/forms/DailyChallengeForm.ets"
}
规则/建议:
- 卡片组件路径必须包含".ets"扩展名
- 确保supportDimensions包含使用的尺寸
- 检查卡片组件是否有语法错误
问题3: 数据无法共享
现象:
原子化服务中的用户数据与主应用不一致。
原因:
- Preferences名称不匹配
- Context使用不正确
错误代码:
// ❌ 错误: 使用不同的preferences名称
const PREFERENCES_NAME = 'service_preferences';
正确代码:
// ✅ 正确: 使用与主应用相同的名称
const PREFERENCES_NAME = 'user_preferences';
规则/建议:
- 主应用和原子化服务使用相同的Preferences名称
- 使用AbilityContext获取Preferences
- 考虑使用分布式数据管理实现跨设备同步
问题4: 路由跳转失败
现象:
从原子化服务跳转到主应用页面失败。
原因:
- 路由路径配置错误
- 页面未在main_pages.json中注册
错误代码:
// ❌ 错误: 路径缺少pages前缀
router.pushUrl({
url: 'Index',
params: { fromService: true }
});
正确代码:
// ✅ 正确: 使用完整路径
router.pushUrl({
url: 'pages/Index',
params: { fromService: true }
}, router.RouterMode.Single);
规则/建议:
- 路由路径必须以"pages/"开头
- 目标页面必须在main_pages.json中注册
- 使用RouterMode.Single避免创建多个实例
问题5: 原子化服务安装失败
现象:
应用安装后,原子化服务没有出现在桌面或服务中心。
原因:
- installationFree配置错误
- 缺少必要的权限声明
错误代码:
// ❌ 错误: 未开启免安装能力
{
"installationFree": false
}
正确代码:
// ✅ 正确: 开启免安装能力
{
"installationFree": true,
"deliveryWithInstall": true
}
规则/建议:
- 必须设置
installationFree: true - 确保
deliveryWithInstall: true - 在requestPermissions中声明必要权限
📝 本章小结
核心知识点
本文详细讲解了HarmonyOS 7原子化服务的开发实践,主要包括:
1. 原子化服务概念
- 原子化服务是轻量化的应用形态
- 支持免安装使用,即点即用
- 适合快速访问的高频功能
2. Module配置
- Module类型设置为"service"
- 配置installationFree和deliveryWithInstall
- 声明服务卡片和快捷入口
3. 卡片开发
- 创建Form组件实现服务卡片
- 配置响应式更新和定时刷新
- 实现卡片生命周期函数
4. 数据共享
- 使用Preferences实现数据共享
- 主应用和服务使用相同的存储名称
- 考虑分布式数据管理
最佳实践总结
✅ 原子化服务Module配置
{
"type": "service",
"installationFree": true,
"deliveryWithInstall": true
}
✅ 服务卡片声明
"extensions": {
"form": {
"forms": [{
"type": "reactive",
"updateEnabled": true,
"defaultDimension": "2x2"
}]
}
}
✅ 生命周期管理
onCreate() { /* 初始化资源 */ }
onDestroy() { /* 释放资源 */ }
onWindowStageCreate() { /* 加载页面 */ }
✅ 数据共享
const PREFERENCES_NAME = 'user_preferences';
下一步预告
在下一篇文章中,我们将:
- 🔄 分布式数据管理:跨设备数据共享与同步
- 📊 实现用户数据的分布式存储
- 🔗 探索多设备协同的开发模式
🔗 相关链接
- 项目源码: Atomgit仓库
💡 提示: 建议结合项目源码阅读,动手实践效果更好!
更多推荐



所有评论(0)