鸿蒙健康应用开发全攻略,鸿蒙系统(HarmonyOS)开发实战

本文从零开始,系统讲解 HarmonyOS 健康应用开发的全流程,涵盖 ArkTS 语言基础、健康数据 API 调用、UI 组件设计、权限配置、调试技巧与发布流程,附带完整代码示例。

前言:鸿蒙生态与健康应用的机遇

2024 年是鸿蒙生态的关键转折年。随着 HarmonyOS NEXT(纯血鸿蒙)的发布,华为正式迈入了"去安卓化"的新阶段。对于开发者来说,这意味着一个全新的、快速增长的应用生态正在形成。

而健康应用,是鸿蒙生态中最具潜力的赛道之一。华为在可穿戴设备(手表、手环)和手机端积累了海量的健康数据能力,这些能力通过 Health Service 和 Sensor Service 对开发者开放。如果你能抓住这个机会,开发出一款优秀的健康应用,就有可能在鸿蒙生态的早期红利中占据先机。

本文将从实际开发角度出发,手把手带你完成一个鸿蒙健康应用的开发。

一、HarmonyOS 开发环境搭建

1.1 开发工具准备

鸿蒙应用开发的官方 IDE 是 DevEco Studio,基于 IntelliJ 平台构建,对前端和 Android 开发者来说上手门槛较低。

环境要求:

项目 要求
操作系统 Windows 10/11 64位、macOS 10.15+、Ubuntu 18.04+
内存 最低8GB,推荐16GB
硬盘 至少10GB可用空间
DevEco Studio 版本 4.1+(支持 HarmonyOS NEXT)
JDK DevEco 内置,无需单独安装
Node.js 16.x+(部分工具链依赖)

安装步骤:

  1. 访问华为开发者官网(developer.huawei.com),下载 DevEco Studio
  2. 运行安装程序,按向导完成安装
  3. 首次启动时,IDE 会自动下载 HarmonyOS SDK
  4. 配置 SDK 路径和模拟器镜像

1.2 创建第一个鸿蒙项目

打开 DevEco Studio,选择"Create Project":


ode>项目类型选择: - Application -> Empty Ability(空应用模板) 项目配置: - Project Name: HealthTracker - Bundle Name: com.example.healthtracker - Save Location: 选择你的工作目录 - Compile SDK: 选择最新的 API 版本 - Model: Stage Model(推荐使用新的舞台模型) - Language: ArkTS(鸿蒙推荐的开发语言)

创建完成后,项目的基本结构如下:

HealthTracker/
├── AppScope/
│   ├── app.json5          # 应用全局配置
│   └── resources/         # 应用级资源
├── entry/
│   ├── src/
│   │   ├── main/
│   │   │   ├── ets/       # ArkTS 源码目录
│   │   │   │   ├── entryability/
│   │   │   │   │   └── EntryAbility.ets
│   │   │   │   └── pages/
│   │   │   │       └── Index.ets
│   │   │   ├── resources/ # 模块级资源
│   │   │   └── module.json5  # 模块配置文件
│   │   └── ohosTest/      # 测试代码
│   ├── build-profile.json5
│   └── hvigorfile.ts
└── build-profile.json5

1.3 模拟器与真机调试

鸿蒙开发支持两种调试方式:

模拟器调试:

DevEco Studio 内置了 HarmonyOS 模拟器,支持手机和穿戴设备形态。在菜单栏选择"Tools -> Device Manager",创建并启动模拟器即可。

模拟器的优点是启动快、不依赖真机,缺点是部分传感器和健康数据 API 无法模拟。

真机调试:

真机调试需要:
1. 在华为开发者官网注册开发者账号
2. 创建应用并获取调试证书
3. 在手机上开启"开发者模式"和"USB调试"
4. 通过 USB 或 WiFi 连接进行调试

对于健康应用开发,强烈建议使用真机调试,因为健康数据 API 和传感器接口在模拟器上无法完整测试。

二、ArkTS 语言基础

ArkTS 是鸿蒙官方推荐的开发语言,基于 TypeScript 扩展而来,增加了声明式 UI 语法和状态管理能力。如果你有 TypeScript 或前端开发经验,上手会非常快。

2.1 ArkTS 与 TypeScript 的区别

特性 TypeScript ArkTS
类型系统 结构化类型 更严格的类型检查
UI 描述 无内置 声明式 UI(@Component、@Builder)
状态管理 需要框架(如 React) 内置(@State、@Prop、@Link)
运行时 V8/Node.js ArkTS 运行时
装饰器 实验性 核心特性

2.2 基本语法

变量声明与类型:

// 基本类型
let count: number

= 0; let name: string = "健康追踪"; let isActive: boolean = true; // 数组 let steps: number[] = [1000, 2000, 3000]; let stepsAlt: Array<number> = [4000, 5000]; // 对象类型 interface HealthRecord { date: string; steps: number; heartRate: number; calories: number; } let record: HealthRecord = { date: "2024-01-15", steps: 8500, heartRate: 72, calories: 320 }; // 联合类型 type Status = "normal" | "warning" | "danger"; let currentStatus: Status = "normal"; // 枚举 enum ExerciseType { Walking = "walking", Running = "running", Cycling = "cycling", Swimming = "swimming" }

2.3 声明式 UI

ArkTS 使用装饰器来声明 UI 组件和状态:

// 一个简单的健康数据卡片组件
@Component
struct HealthCard {
  // @State 标记的变量是组件内部状态,变化时自动触发UI刷新
  @State steps: number = 0;
  @State heartRate: number = 0;
  @State goal: number = 10000;

  build() {
    Column() {
      // 标题行
      Row() {
        Text("今日健康数据")
          .fontSize(20)
          .fontWeight(FontWeight.Bold)
          .fontColor("#333333")
      }
      .width("100%")
      .padding(16)

      // 步数显示
      Row() {
        Text("步数")
          .fontSize(14)
          .fontColor("#666666")
        Blank() // 弹性空白
        Text(`\${this.steps}`)
          .fontSize(28)
          .fontWeight(FontWeight.Bold)
          .fontColor("#FF6B6B")
        Text(` / \${this.goal}`)
          .fontSize(14)
          .fontColor("#999999")
      }
      .width("100%")
      .padding({ left: 16, right: 16 })

      // 进度条
      Progress({
        value: this.steps,
        total: this.goal,
        type: ProgressType.Linear
      })
        .width("90%")
        .height(8)
        .color("#FF6B6B")
        .margin({ top: 8, bottom: 8 })

      // 心率显示
      Row() {
        Text(

"心率") .fontSize(14) .fontColor("#666666") Blank() Text(`\${this.heartRate}`) .fontSize(28) .fontWeight(FontWeight.Bold) .fontColor("#4ECDC4") Text(" bpm") .fontSize(14) .fontColor("#999999") } .width("100%") .padding({ left: 16, right: 16, bottom: 16 }) } .width("100%") .backgroundColor("#FFFFFF") .borderRadius(12) .shadow({ radius: 8, color: "rgba(0,0,0,0.1)" }) } }

2.4 状态管理与数据流

ArkTS 提供了一套完整的状态管理装饰器:

装饰器 作用 数据流向
@State 组件内部状态 组件内
@Prop 父到子单向传递 父 -> 子
@Link 父子双向同步 父 <-> 子
@Provide 跨层级向下传递 祖先 -> 后代
@Consume 消费上层Provide 后代 <- 祖先
@ObjectLink 对象级双向同步 父 <-> 子(对象级别)
@Observed 标记可观察类 配合@ObjectLink使用

示例:父子组件数据传递:

// 可观察的数据类
@Observed
class DailyHealthData {
  steps: number = 0;
  heartRate: number = 0;
  calories: number = 0;
  date: string = "";

  constructor(steps: number, heartRate: number, calories: number) {
    this.steps = steps;
    this.heartRate = heartRate;
    this.calories = calories;
    this.date = new Date().toISOString().split("T")[0];
  }
}

// 父组件
@Component
struct HealthDashboard {
  @State healthData: DailyHealthData = new DailyHealthData(0, 72, 0);
  @State selectedDate: string = "今天";

  build() {
    Column() {
      // 日期选择器
      DatePicker({ selectedDate: this.selectedDate })

      // 数据概览(子组件,使用@Link双向同步)
      Hea

lthOverview({ data: \$healthData }) // 详细数据列表(子组件,使用@Prop单向传递) HealthDetailList({ date: this.selectedDate, steps: this.healthData.steps }) // 更新按钮 Button("刷新数据") .onClick(() => { this.healthData.steps = Math.floor(Math.random() * 15000); this.healthData.heartRate = 60 + Math.floor(Math.random() * 40); this.healthData.calories = Math.floor(Math.random() * 800); }) } } } // 子组件 - 数据概览 @Component struct HealthOverview { @Link data: DailyHealthData; build() { Row() { Column() { Text("步数") .fontSize(12) .fontColor("#999") Text(`\${this.data.steps}`) .fontSize(24) .fontWeight(FontWeight.Bold) } .layoutWeight(1) Column() { Text("心率") .fontSize(12) .fontColor("#999") Text(`\${this.data.heartRate}`) .fontSize(24) .fontWeight(FontWeight.Bold) } .layoutWeight(1) Column() { Text("卡路里") .fontSize(12) .fontColor("#999") Text(`\${this.data.calories}`) .fontSize(24) .fontWeight(FontWeight.Bold) } .layoutWeight(1) } .width("100%") .padding(16) } }

三、HarmonyOS 健康数据 API

3.1 Health Service 概述

HarmonyOS 提供了 Health Service(健康服务),允许应用读取和写入用户的健康数据。主要包括以下数据类型:

数据类型 说明 API前缀
步数 每日步数统计 STEP
心率 实时/历史心率数据 HEART_RATE
睡眠 睡眠时长和质量 SLEEP
运动记录 各类运动数据 EXERCISE
血氧 血氧饱和度 SPO2
体温 体温数据 TEMPERATURE
体重 体重和BMI WEIGHT

3>3.2 权限配置

使用健康数据 API 前,必须在 module.json5 中声明所需权限:

{
  "module": {
    "name": "entry",
    "type": "entry",
    "requestPermissions": [
      {
        "name": "ohos.permission.READ_HEALTH_DATA",
        "reason": "\$string:read_health_reason",
        "usedScene": {
          "abilities": ["EntryAbility"],
          "when": "always"
        }
      },
      {
        "name": "ohos.permission.ACTIVITY_MOTION",
        "reason": "\$string:activity_motion_reason",
        "usedScene": {
          "abilities": ["EntryAbility"],
          "when": "always"
        }
      },
      {
        "name": "ohos.permission.READ_MOTION_SENSOR",
        "reason": "\$string:read_motion_reason",
        "usedScene": {
          "abilities": ["EntryAbility"],
          "when": "always"
        }
      }
    ]
  }
}

同时在 resources/base/element/string.json 中添加权限说明:

{
  "string": [
    {
      "name": "read_health_reason",
      "value": "用于读取您的健康数据,展示每日运动和健康状况"
    },
    {
      "name": "activity_motion_reason",
      "value": "用于获取运动数据,记录您的运动轨迹和消耗"
    },
    {
      "name": "read_motion_reason",
      "value": "用于读取传感器数据,实时监测运动状态"
    }
  ]
}

3.3 运行时动态申请权限

声明权限后,还需要在运行时动态申请用户授权:

import { abilityAccessCtrl, bundleManager } from '@kit.AbilityKit';

class PermissionManager {
  private at

m: abilityAccessCtrl.AtManager = abilityAccessCtrl.createAtManager(); // 检查权限是否已授予 async checkPermission(permission: string): Promise<boolean> { const tokenId = bundleManager.getApplicationInfoSync( bundleManager.getBundleNameForSelf() ).accessTokenId; const status = await this.atm.checkAccessToken(tokenId, permission); return status === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED; } // 请求权限 async requestPermissions(permissions: string[]): Promise<boolean> { const context = getContext(this); const result = await this.atm.requestPermissionsFromUser( context, permissions ); // 检查所有权限是否都被授予 return result.authResults.every( (status) => status === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED ); } // 确保健康数据权限已获取 async ensureHealthPermissions(): Promise<boolean> { const requiredPermissions = [ 'ohos.permission.READ_HEALTH_DATA', 'ohos.permission.ACTIVITY_MOTION' ]; const allGranted = await this.requestPermissions(requiredPermissions); if (!allGranted) { console.warn("部分健康数据权限未授予,相关功能可能受限"); } return allGranted; } }

3.4 读取步数数据

以下是读取用户每日步数的完整示例:

import { sensor } from '@kit.SensorServiceKit';
import { BusinessError } from '@kit.BasicServicesKit';

class StepCounter {
  private stepCount: number = 0;
  private listenerRegistered: boolean = false;

  // 方式一:使用传感器API实时监听步数
  async startStepCounterListening(callback: (steps: number) => void) {
    try {
      sensor.on(sensor.SensorId.PEDOMETER, (data: sensor.PedometerResponse) => {
        this.stepCount = data.steps;
        callback(this.stepCount);
      }, { interval: 'normal' });
      this.listenerRegistered = true;
      console.info("步数监听已启动");
    } catch (error) {
      const e = error as BusinessError;
      console.error(`步数监听启动失败: \${e.code} - \

${e.message}`); } } // 停止步数监听 stopStepCounterListening() { if (this.listenerRegistered) { sensor.off(sensor.SensorId.PEDOMETER); this.listenerRegistered = false; console.info("步数监听已停止"); } } // 方式二:一次性获取当前步数 async getCurrentSteps(): Promise<number> { return new Promise((resolve, reject) => { try { sensor.once(sensor.SensorId.PEDOMETER, (data: sensor.PedometerResponse) => { resolve(data.steps); }); } catch (error) { const e = error as BusinessError; reject(e); } }); } }

3.5 读取心率数据

import { sensor } from '@kit.SensorServiceKit';

class HeartRateMonitor {
  private currentHeartRate: number = 0;
  private isMonitoring: boolean = false;

  // 开始心率监测
  async startHeartRateMonitoring(
    onReading: (heartRate: number) => void,
    onError: (error: string) => void
  ) {
    if (this.isMonitoring) {
      console.warn("心率监测已在运行中");
      return;
    }

    try {
      sensor.on(sensor.SensorId.HEART_RATE, (data: sensor.HeartRateResponse) => {
        this.currentHeartRate = data.heartRate;
        onReading(data.heartRate);

        // 心率异常告警
        if (data.heartRate > 120 || data.heartRate < 50) {
          onError(`心率异常: \${data.heartRate} bpm`);
        }
      }, { interval: 'normal' });

      this.isMonitoring = true;
    } catch (error) {
      onError(`心率监测启动失败: \${JSON.stringify(error)}`);
    }
  }

  // 停止心率监测
  stopHeartRateMonitoring() {
    if (this.isMonitoring) {
      sensor.off(sensor.SensorId.HEART_RATE);
      this.isMonitoring = false;
    }
  }

  // 获取心率区间分析
  getHeartRateZone(heartRate: number, age: number): string {
    const maxHR = 220 - age;
    const ratio = heartRate / maxHR;

    if (ratio < 0.5) return "静息";
    if (ratio < 0.6) return "热身";
    if (ratio < 0.7) return &quo

t;燃脂"; if (ratio < 0.8) return "有氧"; if (ratio < 0.9) return "无氧"; return "极限"; } }

3.6 健康数据存储与历史记录

使用鸿蒙的分布式数据管理(DDS)或关系型数据存储来保存历史健康数据:

import { relationalStore } from '@kit.ArkData';

class HealthDataStore {
  private store: relationalStore.RdbStore | null = null;
  private readonly TABLE_NAME = 'health_records';

  // 初始化数据库
  async init(context: Context) {
    const config: relationalStore.StoreConfig = {
      name: 'HealthTracker.db',
      securityLevel: relationalStore.SecurityLevel.S1
    };

    this.store = await relationalStore.getRdbStore(context, config);

    // 创建表
    const createTableSQL = `
      CREATE TABLE IF NOT EXISTS \${this.TABLE_NAME} (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        date TEXT NOT NULL,
        steps INTEGER DEFAULT 0,
        heart_rate INTEGER DEFAULT 0,
        calories REAL DEFAULT 0,
        sleep_hours REAL DEFAULT 0,
        weight REAL DEFAULT 0,
        exercise_minutes INTEGER DEFAULT 0,
        created_at TEXT DEFAULT (datetime('now', 'localtime'))
      )
    `;

    await this.store.executeSql(createTableSQL);
    console.info("健康数据库初始化完成");
  }

  // 保存今日健康数据
  async saveDailyRecord(record: HealthRecord): Promise<void> {
    if (!this.store) throw new Error("数据库未初始化");

    // 先检查今日是否已有记录
    const predicates = new relationalStore.RdbPredicates(this.TABLE_NAME);
    predicates.equalTo('date', record.date);
    const resultSet = await this.store.query(predicates, ['id']);

    if (resultSet.goToFirstRow()) {
      // 更新现有记录
      const id = resultSet.getLong(resultSet.getColumnIndex('id'));
      resultSet.close();

      const updatePredicates = new relationalStore.RdbPredicates(this.TABLE_NAME);
      updatePredicates.equalTo('id', id);

      const valueBucket: relationalStore.ValuesBucket = {
        'steps': record.steps,
        'he

art_rate': record.heartRate, 'calories': record.calories, 'sleep_hours': record.sleepHours, 'weight': record.weight, 'exercise_minutes': record.exerciseMinutes }; await this.store.update(valueBucket, updatePredicates); } else { resultSet.close(); // 插入新记录 const valueBucket: relationalStore.ValuesBucket = { 'date': record.date, 'steps': record.steps, 'heart_rate': record.heartRate, 'calories': record.calories, 'sleep_hours': record.sleepHours, 'weight': record.weight, 'exercise_minutes': record.exerciseMinutes }; await this.store.insert(this.TABLE_NAME, valueBucket); } } // 查询指定日期范围的数据 async queryRange(startDate: string, endDate: string): Promise<HealthRecord[]> { if (!this.store) throw new Error("数据库未初始化"); const predicates = new relationalStore.RdbPredicates(this.TABLE_NAME); predicates.between('date', startDate, endDate); predicates.orderByAsc('date'); const resultSet = await this.store.query(predicates, [ 'date', 'steps', 'heart_rate', 'calories', 'sleep_hours', 'weight', 'exercise_minutes' ]); const records: HealthRecord[] = []; while (resultSet.goToNextRow()) { records.push({ date: resultSet.getString(resultSet.getColumnIndex('date')), steps: resultSet.getLong(resultSet.getColumnIndex('steps')), heartRate: resultSet.getLong(resultSet.getColumnIndex('heart_rate')), calories: resultSet.getDouble(resultSet.getColumnIndex('calories')), sleepHours: resultSet.getDouble(resultSet.getColumnIndex('sleep_hours')), weight: resultSet.getDouble(resultSet.getColumnIndex('weight')), exerciseMinutes: resultSet.getLong(resultSet.getColumnIndex('exercise_minutes')) }); } resultSet.close(); return records; } // 获取周/月统计 async getWeeklyStats(weekStartDate: string): Promise<WeeklyStats&gt; { const records = await this.queryRange( weekStartDate, this.addDays(weekStartDate, 6) ); const totalSteps = records.reduce((sum, r) => sum + r.steps, 0); const avgHeartRate = records.length > 0 ? Math.round(records.reduce((sum, r) => sum + r.heartRate, 0) / records.length) : 0; const totalCalories = records.reduce((sum, r) => sum + r.calories, 0); const totalSleep = records.reduce((sum, r) => sum + r.sleepHours, 0); const totalExercise = records.reduce((sum, r) => sum + r.exerciseMinutes, 0); return { weekStartDate, avgSteps: Math.round(totalSteps / 7), avgHeartRate, totalCalories, avgSleep: Math.round(totalSleep / 7 * 10) / 10, totalExerciseMinutes: totalExercise }; } private addDays(dateStr: string, days: number): string { const date = new Date(dateStr); date.setDate(date.getDate() + days); return date.toISOString().split('T')[0]; } } interface HealthRecord { date: string; steps: number; heartRate: number; calories: number; sleepHours: number; weight: number; exerciseMinutes: number; } interface WeeklyStats { weekStartDate: string; avgSteps: number; avgHeartRate: number; totalCalories: number; avgSleep: number; totalExerciseMinutes: number; }

四、UI 组件与页面设计

4.1 首页 - 健康数据仪表盘

@Component
struct HealthDashboardPage {
  @State currentSteps: number = 0;
  @State currentHeartRate: number = 0;
  @State goalSteps: number = 10000;
  @State todayCalories: number = 0;
  @State exerciseMinutes: number = 0;
  @State isLoading: boolean = true;

  private stepCounter: StepCounter = new StepCounter();
  private heartRateMonitor: HeartRateMonitor = new HeartRateMonitor();

  aboutToAppear() {
    this.loadTodayData();
    this.startRealtimeMonitoring();
  }

  aboutToDisappear() {
    this.stepCounter.stopStepCounterListening();

this.heartRateMonitor.stopHeartRateMonitoring(); } async loadTodayData() { // 从数据库加载今日数据 // 此处省略数据库调用细节 this.isLoading = false; } startRealtimeMonitoring() { this.stepCounter.startStepCounterListening((steps) => { this.currentSteps = steps; }); this.heartRateMonitor.startHeartRateMonitoring( (heartRate) => { this.currentHeartRate = heartRate; }, (error) => { console.warn(error); } ); } build() { Column() { // 顶部标题栏 Row() { Text("健康追踪") .fontSize(24) .fontWeight(FontWeight.Bold) .fontColor("#1A1A1A") Blank() Image(\$r("app.media.settings_icon")) .width(24) .height(24) .onClick(() => { // 跳转设置页 }) } .width("100%") .height(56) .padding({ left: 20, right: 20 }) // 可滚动内容区 Scroll() { Column() { // 步数环形进度 StepRingProgress({ current: this.currentSteps, goal: this.goalSteps }) // 数据卡片网格 Row() { DataCard({ title: "心率", value: this.currentHeartRate.toString(), unit: "bpm", color: "#FF6B6B", icon: \$r("app.media.heart_icon") }) DataCard({ title: "卡路里", value: this.todayCalories.toString(), unit: "kcal", color: "#FFA502", icon: \$r("app.media.fire_icon") }) } .width("100%") .padding({ left: 16, right: 16 }) .margin({ top: 16 }) Row() { DataCard({ title: "运动", value: this.exerciseMinutes.toString(), unit: "分钟", color: "#4ECDC4", icon: \$r("app.media.exercise_icon") }) DataCard({ title: "目标", value: `\${Math.round(this.currentSteps / this.goalSteps * 100)}`, unit: "%", color: "#5352ED", icon: \$r("app.media.target_icon") }) } .width("100%") .padding({ left: 16, right: 16 }) .margin({ top: 12 }) // 周趋势图 WeeklyTrendChart() .margin({ top: 16 }) // 快捷操作 Row() { Button("开始运动") { // 跳转运动记录页 } .backgroundColor("#FF6B6B") .fontColor("#FFFFFF") .borderRadius(24) .height(48) .layoutWeight(1) Button("手动记录") { // 跳转手动记录页 } .backgroundColor("#F1F2F6") .fontColor("#333333") .borderRadius(24) .height(48) .layoutWeight(1) .margin({ left: 12 }) } .width("100%") .padding(16) .margin({ top: 16, bottom: 24 }) } } .layoutWeight(1) } .width("100%") .height("100%") .backgroundColor("#F8F9FA") } } // 步数环形进度组件 @Component struct StepRingProgress { @Prop current: number; @Prop goal: number; build() { Stack({ alignContent: Alignment.Center }) { // 背景圆环 Progress({ value: Math.min(this.current / this.goal * 100, 100), total: 100, type: ProgressType.Ring }) .width(200) .height(200) .color("#FF6B6B") .backgroundColor("#F1F2F6") .style({ strokeWidth: 16 }) // 中心文字 Column() { Text("步数") .fontSize(14) .fontColor("#999999") Text(`\${this.current}`) .fontSize(36) .fontWeight(FontWeight.Bold) .fontColor("#1A1A1A") .margin({ top: 4 }) Text(`目标 \${this.goal}`) .fontSize(12) .fontColor("#CCCCCC") .margin({ top: 4 }) } } .width("100%") .margin({ top: 24, bottom: 24 }) } } // 数据卡片组件 @Component struct DataCard { @Prop title: string; @Prop value: string; @Prop unit: string; @Prop color: string; @Prop icon: Resource; build() { Column() { Row() { Image(this.icon) .width(20) .height(20) .fillColor(this.color) Text(this.title) .fontSize(12) .fontColor("#999999") .margin({ left: 6 }) } Row() { Text(this.value) .fontSize(24) .fontWeight(FontWeight.Bold) .fontColor(this.color) Text(` \${this.unit}`) .fontSize(12) .fontColor("#CCCCCC") } .margin({ top: 8 }) } .layoutWeight(1) .height(80) .backgroundColor("#FFFFFF") .borderRadius(12) .padding(16) .alignItems(HorizontalAlign.Start) } }

4.2 周趋势图表

使用 ArkTS 的 Canvas 组件绘制自定义图表:

@Component
struct WeeklyTrendChart {
  @State weeklyData: number[] = [6200, 8500, 7100, 9300, 8800, 10200, 7600];
  private weekDays: string[] = ["周一", "周二", "周三", "周四", "周五", "周六", "周日"];

  build() {
    Column() {
      Text("本周步数趋势")
        .fontSize(16)
        .fontWeight(FontWeight.Bold)
        .fontColor("#333333")
        .margin({ bottom: 16 })

      // 使用Canvas绘制柱状图
      Canvas(this.canvasContext)
        .width("100%")
        .height(18

0) .onReady(() => { this.drawChart(); }) } .width("100%") .backgroundColor("#FFFFFF") .borderRadius(12) .padding(16) .margin({ left: 16, right: 16 }) } private canvasContext: CanvasRenderingContext2D = new CanvasRenderingContext2D(new RenderingContextSettings(true)); private drawChart() { const ctx = this.canvasContext; const width = ctx.width; const height = ctx.height; const padding = 20; const barWidth = (width - padding * 2) / 7 * 0.6; const gap = (width - padding * 2) / 7 * 0.4; const maxData = Math.max(...this.weeklyData, 10000); const chartHeight = height - 40; // 留出底部标签空间 ctx.clearRect(0, 0, width, height); // 绘制柱状图 this.weeklyData.forEach((value, index) => { const barHeight = (value / maxData) * chartHeight; const x = padding + index * (barWidth + gap); const y = chartHeight - barHeight; // 渐变色 const gradient = ctx.createLinearGradient(0, y, 0, chartHeight); gradient.addColorStop(0, "#FF6B6B"); gradient.addColorStop(1, "#FF8E8E"); ctx.fillStyle = gradient; ctx.fillRect(x, y, barWidth, barHeight); // 绘制数值 ctx.fillStyle = "#666666"; ctx.font = "10px sans-serif"; ctx.textAlign = "center"; ctx.fillText(`\${value}`, x + barWidth / 2, y - 5); // 绘制日期标签 ctx.fillStyle = "#999999"; ctx.font = "11px sans-serif"; ctx.fillText(this.weekDays[index], x + barWidth / 2, height - 5); }); // 绘制目标线 const goalY = chartHeight - (10000 / maxData) * chartHeight; ctx.strokeStyle = "#4ECDC4"; ctx.lineWidth = 1; ctx.setLineDash([4, 4]); ctx.beginPath(); ctx.moveTo(padding, goalY); ctx.lineTo(width - padding, goalY); ctx.stroke(); ctx.setLineDash([]); ctx.fillStyle = "#4ECDC4"; ctx.font = "10px sans-serif"; ctx.textAlign = "left"; ctx.fillText("目标 10000", padding + 4, goalY - 4); } }

五、运动记录功能开发

5.1 运动类型与数据模型

// 运动类型枚举
enum ExerciseType {
  WALKING = "步行",
  RUNNING = "跑步",
  CYCLING = "骑行",
  SWIMMING = "游泳",
  YOGA = "瑜伽",
  STRENGTH = "力量训练"
}

// 运动强度
enum ExerciseIntensity {
  LOW = "低强度",
  MEDIUM = "中等强度",
  HIGH = "高强度"
}

// 运动记录数据模型
class ExerciseRecord {
  id: number = 0;
  type: ExerciseType;
  startTime: number;  // 时间戳
  duration: number;   // 持续时间(秒)
  distance: number;   // 距离(米)
  calories: number;   // 消耗卡路里
  avgHeartRate: number;
  maxHeartRate: number;
  intensity: ExerciseIntensity;
  gpsTrack: GPSPoint[];  // GPS轨迹点

  constructor(type: ExerciseType) {
    this.type = type;
    this.startTime = Date.now();
    this.duration = 0;
    this.distance = 0;
    this.calories = 0;
    this.avgHeartRate = 0;
    this.maxHeartRate = 0;
    this.intensity = ExerciseIntensity.MEDIUM;
    this.gpsTrack = [];
  }

  // 计算平均配速(分钟/公里)
  getAvgPace(): number {
    if (this.distance === 0 || this.duration === 0) return 0;
    const km = this.distance / 1000;
    const minutes = this.duration / 60;
    return minutes / km;
  }

  // 格式化时长
  getFormattedDuration(): string {
    const hours = Math.floor(this.duration / 3600);
    const minutes = Math.floor((this.duration % 3600) / 60);
    const seconds = this.duration % 60;
    if (hours > 0) {
      return `\${hours}:\${String(minutes).padStart(2, '0')}:\${String(seconds).padStart(2, '0')}`;
    }
    return `\${minutes}:\${String(seconds).padStart(2, '0')}`;
  }
}

interface GPSPoint {
  latitude: number;
  longitude: number;
  timestamp: number;
  altitude: number;
}

5.2 实时运动追踪

import { geoLocationManager

} from '@kit.LocationKit'; import { sensor } from '@kit.SensorServiceKit'; class ExerciseTracker { private currentRecord: ExerciseRecord | null = null; private isTracking: boolean = false; private heartRateReadings: number[] = []; // 开始运动追踪 async startTracking(type: ExerciseType): Promise<void> { if (this.isTracking) { console.warn("已有运动正在追踪中"); return; } this.currentRecord = new ExerciseRecord(type); this.heartRateReadings = []; this.isTracking = true; // 启动GPS定位 await this.startLocationTracking(); // 启动心率监测 this.startHeartRateTracking(); console.info(`开始追踪 \${type} 运动`); } // 停止追踪并返回记录 async stopTracking(): Promise<ExerciseRecord | null> { if (!this.isTracking || !this.currentRecord) { return null; } this.isTracking = false; // 停止定位和心率 this.stopLocationTracking(); this.stopHeartRateTracking(); // 计算统计数据 const record = this.currentRecord; record.duration = Math.floor((Date.now() - record.startTime) / 1000); if (this.heartRateReadings.length > 0) { record.avgHeartRate = Math.round( this.heartRateReadings.reduce((a, b) => a + b, 0) / this.heartRateReadings.length ); record.maxHeartRate = Math.max(...this.heartRateReadings); } // 估算卡路里(简化版) record.calories = this.estimateCalories(record); this.currentRecord = null; return record; } // GPS定位追踪 private async startLocationTracking() { const request: geoLocationManager.LocationRequest = { 'priority': geoLocationManager.LocationRequestPriority.HIGH_ACCURACY, 'scenario': geoLocationManager.LocationRequestScenario.UNSET, 'timeInterval': 1, 'distanceInterval': 5, 'maxAccuracy': 0 }; geoLocationManager.on('locationChange', request, (location: geoLocationManager.Location) => { if (!this.currentRecord) return; const point: GPSPoint = { latitude: location.latitude, longitude: location.longitude, timestamp: Date.now(), altitude: location.altitude }; this.currentRecord.gpsTrack.push(point); // 计算累计距离 if (this.currentRecord.gpsTrack.length >= 2) { const prev = this.currentRecord.gpsTrack[ this.currentRecord.gpsTrack.length - 2 ]; const dist = this.calculateDistance( prev.latitude, prev.longitude, point.latitude, point.longitude ); this.currentRecord.distance += dist; } }); } private stopLocationTracking() { geoLocationManager.off('locationChange'); } // 心率追踪 private startHeartRateTracking() { sensor.on(sensor.SensorId.HEART_RATE, (data: sensor.HeartRateResponse) => { if (data.heartRate > 0) { this.heartRateReadings.push(data.heartRate); } }, { interval: 'normal' }); } private stopHeartRateTracking() { sensor.off(sensor.SensorId.HEART_RATE); } // 计算两点间距离(Haversine公式) private calculateDistance( lat1: number, lon1: number, lat2: number, lon2: number ): number { const R = 6371000; // 地球半径(米) const dLat = this.toRad(lat2 - lat1); const dLon = this.toRad(lon2 - lon1); const a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + Math.cos(this.toRad(lat1)) * Math.cos(this.toRad(lat2)) * Math.sin(dLon / 2) * Math.sin(dLon / 2); const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); return R * c; } private toRad(deg: number): number { return deg * (Math.PI / 180); } // 卡路里估算(基于MET值) private estimateCalories(record: ExerciseRecord): number { // MET值参考 const metValues: Record<ExerciseType, number> = { [ExerciseType.WALKING]: 3.5, [ExerciseType.RUNNING]: 9.8, [ExerciseType.CYCLING]: 7.5, [ExerciseType.SWIMMING]: 8.0, [ExerciseType.YOGA]: 3.0, [ExerciseType.STRENGTH]: 6.0 }; const met = metValues[record.type] || 5.0; const weight = 65; // 默认体重65kg const hours = record.duration / 3600; return Math.round(met * weight * hours); } }

六、数据同步与分布式能力

6.1 分布式数据同步

鸿蒙的分布式能力是其核心优势之一。通过分布式数据管理,健康数据可以在手机、手表、平板等设备间自动同步:

import { distributedKVStore } from '@kit.ArkData';
import { deviceInfo } from '@kit.BasicServicesKit';

class DistributedHealthData {
  private kvManager: distributedKVStore.KVManager | null = null;
  private kvStore: distributedKVStore.SingleKVStore | null = null;

  async init(context: Context) {
    // 创建KVManager
    const managerConfig: distributedKVStore.KVManagerConfig = {
      context: context,
      bundleName: 'com.example.healthtracker'
    };
    this.kvManager = distributedKVStore.createKVManager(managerConfig);

    // 创建分布式KVStore
    const storeConfig: distributedKVStore.Options = {
      createIfMissing: true,
      encrypt: false,
      backup: true,
      autoSync: true,
      kvStoreType: distributedKVStore.KVStoreType.SINGLE_VERSION,
      securityLevel: distributedKVStore.SecurityLevel.S1
    };

    this.kvStore = await this.kvManager.getKVStore(
      'health_data_store',
      storeConfig
    );

    // 监听其他设备的数据变更
    this.kvStore.on('dataChange', 
      distributedKVStore.SubscribeType.SUBSCRIBE_TYPE_REMOTE,
      (callback) => {
        console.info("收到远程设备数据变更");
        callback.insertEntries.forEach((value, key) => {
          console.info(`同步数据: \${key} = \${value}`);
          this.handleSyncedData(key, value);
        });
      }
    );
  }

  // 写入数据(自动同步到其他设备)
  async saveHealthData(key: string, data: string) {
    if (!this.kvStore) return;
    await this.kvStore.put(key, data);
    console.info(`数据已保存并开始同步: \${key}`);
  }

  // 处理同步数据
  private handleSyncedData(key: string, value: string) {
    // 解析并更新本地UI
    const data = JSON.parse(value);
    // 根据key的前缀分发处理
    if (key.startsWith('steps_')) {
      // 更新步数显示
    } else 

if (key.startsWith('heart_rate_')) { // 更新心率显示 } } }

七、通知与提醒功能

7.1 久坐提醒

import { notificationManager } from '@kit.NotificationKit';
import { BackgroundTaskManager } from '@kit.BackgroundTasksKit';

class HealthReminder {
  private sitTimer: number = 0;
  private readonly SIT_THRESHOLD: number = 60; // 60分钟久坐阈值

  // 启动久坐检测
  startSitDetection() {
    // 每分钟检查一次
    setInterval(() => {
      this.sitTimer++;
      if (this.sitTimer >= this.SIT_THRESHOLD) {
        this.sendSitReminder();
        this.sitTimer = 0;
      }
    }, 60000);
  }

  // 用户活动时重置计时器
  onUserActivity() {
    this.sitTimer = 0;
  }

  private async sendSitReminder() {
    const notificationRequest: notificationManager.NotificationRequest = {
      id: 1,
      content: {
        contentType: notificationManager.ContentType.NOTIFICATION_CONTENT_BASIC_TEXT,
        normal: {
          title: "久坐提醒",
          text: "您已经久坐60分钟了,站起来活动一下吧!",
          additionalText: "健康追踪"
        }
      },
      deliveryTime: new Date().getTime(),
      notificationSlotType: notificationManager.SlotType.SOCIAL_COMMUNICATION
    };

    await notificationManager.publish(notificationRequest);
  }

  // 运动目标提醒
  async sendGoalReminder(currentSteps: number, goal: number) {
    if (currentSteps >= goal) {
      const notificationRequest: notificationManager.NotificationRequest = {
        id: 2,
        content: {
          contentType: notificationManager.ContentType.NOTIFICATION_CONTENT_BASIC_TEXT,
          normal: {
            title: "目标达成!",
            text: `恭喜!您今天已完成 \${currentSteps} 步,达到了目标!`,
            additionalText: "健康追踪"
          }
        }
      };
      await notificationManager.publish(notificationRequest);
    }
  }

  // 喝水提醒
  startWaterReminder() {
    // 每2小时提醒一次
    setInterval(async () => {
      const notificationRequest: noti

ficationManager.NotificationRequest = { id: 3, content: { contentType: notificationManager.ContentType.NOTIFICATION_CONTENT_BASIC_TEXT, normal: { title: "喝水提醒", text: "该喝水了!保持充足的水分摄入对健康很重要。", additionalText: "健康追踪" } } }; await notificationManager.publish(notificationRequest); }, 7200000); // 2小时 } }

八、应用发布流程

8.1 发布前检查清单

在提交应用商店之前,需要完成以下检查:

检查项 说明 状态
应用签名 使用正式证书签名 待检查
权限说明 所有权限需有中文说明 待检查
隐私政策 提供隐私政策URL 待检查
应用图标 512x512 px PNG 待检查
应用截图 至少3张,推荐5张 待检查
应用描述 简明扼要,突出特色 待检查
版本号 遵循语义化版本规范 待检查
适配测试 多设备适配验证 待检查

8.2 签名与打包

# 1. 生成密钥库
keytool -genkey -alias health_key -keyalg RSA -keysize 2048 \
  -validity 36500 -keystore health_keystore.p12

# 2. 在DevEco Studio中配置签名
#    Project Structure -> Project -> Signing Configs
#    配置Store File、Store Password、Key Alias、Key Password

# 3. 构建Release版本
#    Build -> Build Hap(s)/APP(s) -> Build Release Hap(s)

# 4. 生成的产物在 build/outputs/ 目录下

8.3 上架华为应用市场

  1. 登录华为开发者联盟(developer.huawei.com)
  2. 进入"AppGallery Connect"
  3. 创建应用,填写基本信息
  4. 上传应用包(.app 或 .hap 文件)
  5. 填写应用详情(描述、截图、分类等)
  6. 提交审核(通常3-5个工作日)
  7. 审核通过后自动上架

审核注意事项:

  • 健康类应用需要提供相关资质证明
  • 涉及医疗建议的功能需要特别声明
  • 隐私政策必须明确说明数据收集和使用方式
  • 应用内不能有虚假或夸大的健康效果宣传

九、性能优化与最佳实践

9.1 UI性能优化


guage-typescript">// 优化前:在build中创建对象(每次刷新都创建新对象) @Component struct BadExample { @State items: string[] = []; build() { // 错误:每次UI刷新都创建新的RegExp对象 List() { ForEach(this.items, (item: string) => { ListItem() { Text(item.replace(new RegExp('\\d+', 'g'), 'N')) } }) } } } // 优化后:预编译正则,使用LazyForEach @Component struct GoodExample { @State items: string[] = []; private numberPattern: RegExp = new RegExp('\\d+', 'g'); // 预编译 build() { List({ space: 8 }) { // 使用LazyForEach按需加载 LazyForEach(new HealthDataAdapter(this.items), (item: string) => { ListItem() { Text(item.replace(this.numberPattern, 'N')) } }, (item: string) => item) } } } // 数据适配器 class HealthDataAdapter implements IDataSource { private items: string[] = []; private listeners: DataChangeListener[] = []; constructor(items: string[]) { this.items = items; } totalCount(): number { return this.items.length; } getData(index: number): string { return this.items[index]; } registerDataChangeListener(listener: DataChangeListener): void { this.listeners.push(listener); } unregisterDataChangeListener(listener: DataChangeListener): void { const pos = this.listeners.indexOf(listener); if (pos >= 0) { this.listeners.splice(pos, 1); } } }

9.2 传感器数据采样优化

class OptimizedSensorManager {
  // 降采样:减少传感器数据的采样频率
  private lastStepUpdate: number = 0;
  private readonly STEP_UPDATE_INTERVAL: number = 5000; // 5秒更新一次UI

  onStepData(steps: number) {
    const now = Date.now();
    if (now - this.lastStepUpdate < this.STEP_UPDATE_INTERVAL) {
      return; // 降采样,跳过这次更新
    }
    this.lastStepUpdate = now;
    // 更新UI
  }

  // 批量处理心率数据
  private heartRateBuffer: number[] = [];
  private readonly BUFFER_SIZE: number = 10;

  onHeartRateData(heartRate: number) {
    thi

s.heartRateBuffer.push(heartRate); if (this.heartRateBuffer.length >= this.BUFFER_SIZE) { const avg = this.heartRateBuffer.reduce((a, b) => a + b, 0) / this.BUFFER_SIZE; // 批量更新UI this.heartRateBuffer = []; } } }

十、完整项目架构总结

10.1 项目目录结构

HealthTracker/
├── AppScope/
│   └── app.json5
├── entry/
│   └── src/main/
│       ├── ets/
│       │   ├── entryability/
│       │   │   └── EntryAbility.ets
│       │   ├── pages/
│       │   │   ├── Index.ets              # 首页 - 仪表盘
│       │   │   ├── ExercisePage.ets       # 运动记录页
│       │   │   ├── HistoryPage.ets        # 历史数据页
│       │   │   ├── SettingsPage.ets       # 设置页
│       │   │   └── DetailPage.ets         # 数据详情页
│       │   ├── components/
│       │   │   ├── HealthCard.ets         # 健康数据卡片
│       │   │   ├── StepRingProgress.ets   # 步数环形进度
│       │   │   ├── DataCard.ets           # 数据卡片
│       │   │   └── WeeklyTrendChart.ets   # 周趋势图表
│       │   ├── model/
│       │   │   ├── HealthRecord.ets       # 健康记录模型
│       │   │   └── ExerciseRecord.ets     # 运动记录模型
│       │   ├── service/
│       │   │   ├── StepCounter.ets        # 步数服务
│       │   │   ├── HeartRateMonitor.ets   # 心率服务
│       │   │   ├── ExerciseTracker.ets    # 运动追踪服务
│       │   │   ├── HealthDataStore.ets    # 数据存储服务
│       │   │   └── HealthReminder.ets     # 健康提醒服务
│       │   ├── utils/
│       │   │   ├── PermissionManager.ets  # 权限管理
│       │   │   └── DateUtils.ets          # 日期工具
│       │   └── common/
│       │       └── Constants.ets          # 常量定义
│       ├── resources/
│       │   └── base/
│       │       ├── element/
│       │       │   ├── string.json
│       │       │   └── color.json
│       │       └── media/
│       │           ├── heart_icon.png
│       │           └── fire_icon.png
│       └── module.json5
└── build-profile.json5

10.2 技术选型总结

模块 技术方案 说明

h> 开发语言 ArkTS 鸿蒙推荐语言 UI框架 ArkUI 声明式 内置状态管理 数据存储 relationalStore 关系型数据库 分布式同步 distributedKVStore KV存储自动同步 传感器 @kit.SensorServiceKit 步数、心率等 定位 @kit.LocationKit GPS轨迹追踪 通知 @kit.NotificationKit 健康提醒推送 权限 @kit.AbilityKit 运行时权限管理

十一、鸿蒙健康应用开发注意事项

11.1 合规性要求

健康数据属于敏感个人信息,开发时必须严格遵守相关法律法规:

  1. 数据采集合规:必须明确告知用户采集哪些数据、用途是什么,并获得用户明示同意
  2. 数据存储安全:健康数据应加密存储,不得明文保存
  3. 数据使用限制:不得将健康数据用于用户未授权的用途
  4. 隐私政策:应用必须提供完整的隐私政策,说明数据的收集、使用、存储和共享方式
  5. 用户权利:用户有权查看、导出和删除自己的健康数据

11.2 兼容性考量

鸿蒙生态目前处于快速迭代期,不同版本的 API 可能存在差异:

  • API 9 及以下:主要支持 HarmonyOS 3.x 设备
  • API 10-11:支持 HarmonyOS 4.x 设备
  • API 12+:支持 HarmonyOS NEXT 设备

建议在 build-profile.json5 中配置合理的最低兼容版本,并在代码中使用 API 版本判断:

import { deviceInfo } from '@kit.BasicServicesKit';

function getApiVersion(): number {
  return deviceInfo.firstApiVersion;
}

// 根据API版本选择不同的实现
if (getApiVersion() >= 12) {
  // 使用 HarmonyOS NEXT 的新API
} else {
  // 使用兼容方案
}

11.3 电池优化

健康应用通常需要持续监测传感器数据,这对电池消耗较大。以下是一些优化建议:

  1. 合理设置采样间隔:不要使用最高频率采样,根据实际需求选择
  2. 后台任务管理:使用鸿蒙的后台任务管理 API,确保应用在后台时不会过度消耗电量
  3. 批量处理:将传感器数据批量处理后再更新UI,减少频繁的UI刷新
  4. 按需启停:在不需要监测时及时关闭传感器监听

十二、总结

鸿蒙健康应用开发是一个充满机遇的领域。通过本文的系统讲解,你应该已经对以下内容有了清晰的理解:

  1. 开发环境搭建:DevEco Studio 的安装配置和项目创建
  2. ArkTS 语言基础:声明式 UI 语法和状态管理
  3. <

strong>健康数据 API:步数、心率等传感器数据读取

  • 数据存储:关系型数据库和分布式数据同步
  • UI 组件设计:仪表盘、图表、数据卡片的实现
  • 运动追踪:GPS 定位、实时心率监测、卡路里计算
  • 通知提醒:久坐提醒、目标达成通知
  • 发布流程:签名打包、应用市场上架
  • 性能优化:UI性能、传感器采样优化
  • 合规与兼容:数据合规、版本兼容、电池优化

鸿蒙生态正在快速成长,现在入场正是最好的时机。希望这篇文章能帮助你快速上手鸿蒙健康应用开发,打造出优秀的健康类应用。

如果你在开发过程中遇到问题,可以参考华为开发者文档(developer.huawei.com),或者在开发者社区中寻求帮助。鸿蒙开发者社区目前非常活跃,很多问题都能找到解决方案。

祝开发顺利!


本文基于 HarmonyOS NEXT API 12+ 编写,部分 API 可能随版本更新而变化,请以官方最新文档为准。文中代码示例已通过基本功能验证,但在生产环境中使用前请进行充分测试。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐