在这里插入图片描述

📖 引言

随着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"表示这是一个原子化服务Module
  • deliveryWithInstall: true表示随应用安装一起交付
  • installationFree: true支持免安装使用

2. ServiceAbility配置

{
  "name": "DailyChallengeService",
  "type": "service",
  "visible": true,
  "exported": true
}

原理/说明:

  • type: "service"表示这是一个Service类型的Ability
  • visible: 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';

下一步预告

在下一篇文章中,我们将:

  • 🔄 分布式数据管理:跨设备数据共享与同步
  • 📊 实现用户数据的分布式存储
  • 🔗 探索多设备协同的开发模式

🔗 相关链接


💡 提示: 建议结合项目源码阅读,动手实践效果更好!

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐