基于HarmonyOS Next的智能健康监测应用开发实战

基于HarmonyOS Next的智能健康监测应用开发实战

一、项目概述

随着人们健康意识的提升,健康管理类应用越来越受到欢迎。本教程将带领开发者使用DevEco Studio和ArkTS语言,开发一款基于HarmonyOS Next的智能健康监测应用。该应用将实现以下核心功能:

  1. 实时心率监测与可视化
  2. 每日步数统计与分析
  3. 睡眠质量评估
  4. 健康数据云端同步

本教程面向有一定HarmonyOS开发基础的开发者,通过完整的项目实战,帮助掌握鸿蒙健康管理类应用的开发技巧。

二、开发环境准备

在开始开发前,请确保已完成以下准备工作:

  1. 安装最新版DevEco Studio(建议4.0及以上版本)
  2. 配置HarmonyOS Next开发环境
  3. 准备一台支持鸿蒙系统的真机设备(或使用模拟器)
  4. 申请健康数据访问权限
// 检查设备健康数据权限
import abilityAccessCtrl from **********';

async function checkHealthPermission(): Promise<boolean> {
  try {
    const atManager = abilityAccessCtrl.createAtManager();
    const grantStatus = await atManager.checkAccessToken(
      abilityAccessCtrl.ATokenTypeEnum.TOKEN_NATIVE,
      'ohos.permission.READ_HEALTH_DATA'
    );
    return grantStatus === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED;
  } catch (err) {
    console.error(`Check permission failed, code is ${err.code}, message is ${err.message}`);
    return false;
  }
}

三、项目结构设计

我们的健康监测应用将采用以下模块化结构:

health_monitor/
├── entry/
│   ├── src/
│   │   ├── main/
│   │   │   ├── ets/
│   │   │   │   ├── components/      # 自定义组件
│   │   │   │   ├── model/          # 数据模型
│   │   │   │   ├── pages/          # 页面
│   │   │   │   ├── service/        # 服务层
│   │   │   │   └── utils/          # 工具类
│   │   │   └── resources/          # 资源文件

四、核心功能实现

4.1 心率监测模块

// src/main/ets/service/HeartRateService.ets
import { HealthData, HealthType } from **********';
import { BusinessError } from **********';

export class HeartRateService {
  private static instance: HeartRateService | null = null;
  private healthData: HealthData | null = null;

  private constructor() {
    this.initHealthData();
  }

  public static getInstance(): HeartRateService {
    if (!HeartRateService.instance) {
      HeartRateService.instance = new HeartRateService();
    }
    return HeartRateService.instance;
  }

  private async initHealthData(): Promise<void> {
    try {
      this.healthData = await HealthData.createInstance();
      console.info('HealthData instance created successfully');
    } catch (error) {
      console.error(`Failed to create HealthData instance, error: ${JSON.stringify(error)}`);
    }
  }

  // 获取实时心率数据
  public async getRealTimeHeartRate(callback: (value: number) => void): Promise<void> {
    if (!this.healthData) {
      console.error('HealthData is not initialized');
      return;
    }

    try {
      await this.healthData.startReading({
        dataType: HealthType.HealthDataType.HEART_RATE,
        callback: (data) => {
          if (data && data.length > 0) {
            const heartRate = data[0].value;
            callback(heartRate);
          }
        }
      });
    } catch (error) {
      const err: BusinessError = error as BusinessError;
      console.error(`Failed to read heart rate, code: ${err.code}, message: ${err.message}`);
    }
  }

  // 停止心率监测
  public async stopHeartRateMonitoring(): Promise<void> {
    if (!this.healthData) return;
    
    try {
      await this.healthData.stopReading({
        dataType: HealthType.HealthDataType.HEART_RATE
      });
      console.info('Heart rate monitoring stopped');
    } catch (error) {
      console.error(`Failed to stop heart rate monitoring: ${JSON.stringify(error)}`);
    }
  }
}

4.2 步数统计模块

// src/main/ets/service/StepCounterService.ets
import { HealthData, HealthType } from **********';
import { BusinessError } from **********';

export class StepCounterService {
  private static instance: StepCounterService | null = null;
  private healthData: HealthData | null = null;

  private constructor() {
    this.initHealthData();
  }

  public static getInstance(): StepCounterService {
    if (!StepCounterService.instance) {
      StepCounterService.instance = new StepCounterService();
    }
    return StepCounterService.instance;
  }

  private async initHealthData(): Promise<void> {
    try {
      this.healthData = await HealthData.createInstance();
    } catch (error) {
      console.error(`Failed to create HealthData instance: ${JSON.stringify(error)}`);
    }
  }

  // 获取今日步数
  public async getTodaySteps(): Promise<number> {
    if (!this.healthData) return 0;

    try {
      const now = new Date();
      const startOfDay = new Date(now.getFullYear(), now.getMonth(), now.getDate());
      
      const result = await this.healthData.read({
        dataType: HealthType.HealthDataType.STEP_COUNT,
        startTime: startOfDay.getTime(),
        endTime: now.getTime()
      });

      if (result && result.length > 0) {
        return result.reduce((total, data) => total + data.value, 0);
      }
      return 0;
    } catch (error) {
      const err: BusinessError = error as BusinessError;
      console.error(`Failed to read step count, code: ${err.code}, message: ${err.message}`);
      return 0;
    }
  }

  // 获取步数历史数据
  public async getStepHistory(days: number): Promise<Array<{date: string, steps: number}>> {
    if (!this.healthData || days <= 0) return [];

    try {
      const now = new Date();
      const startDate = new Date(now);
      startDate.setDate(now.getDate() - days);
      
      const result = await this.healthData.read({
        dataType: HealthType.HealthDataType.STEP_COUNT,
        startTime: startDate.getTime(),
        endTime: now.getTime(),
        timeUnit: HealthType.TimeUnit.DAY
      });

      const stepsByDay: Array<{date: string, steps: number}> = [];
      
      if (result && result.length > 0) {
        result.forEach(data => {
          const date = new Date(data.startTime);
          const dateStr = `${date.getFullYear()}-${date.getMonth()+1}-${date.getDate()}`;
          stepsByDay.push({
            date: dateStr,
            steps: data.value
          });
        });
      }

      return stepsByDay;
    } catch (error) {
      console.error(`Failed to read step history: ${JSON.stringify(error)}`);
      return [];
    }
  }
}

五、UI界面开发

5.1 主页面布局

// src/main/ets/pages/Index.ets
import { HeartRateService } from '../service/HeartRateService';
import { StepCounterService } from '../service/StepCounterService';

@Entry
@Component
struct Index {
  @State heartRate: number = 0;
  @State todaySteps: number = 0;
  @State isMonitoring: boolean = false;
  private heartRateService: HeartRateService = HeartRateService.getInstance();
  private stepService: StepCounterService = StepCounterService.getInstance();

  aboutToAppear() {
    this.loadStepData();
  }

  async loadStepData() {
    this.todaySteps = await this.stepService.getTodaySteps();
  }

  toggleHeartRateMonitoring() {
    if (this.isMonitoring) {
      this.heartRateService.stopHeartRateMonitoring();
      this.isMonitoring = false;
    } else {
      this.heartRateService.getRealTimeHeartRate((rate) => {
        this.heartRate = rate;
      });
      this.isMonitoring = true;
    }
  }

  build() {
    Column() {
      // 顶部标题
      Text('健康监测')
        .fontSize(24)
        .fontWeight(FontWeight.Bold)
        .margin({ top: 20, bottom: 30 })

      // 心率监测卡片
      HealthCard({
        title: '心率监测',
        value: this.heartRate,
        unit: 'BPM',
        icon: $r('app.media.heart_icon'),
        isActive: this.isMonitoring,
        onToggle: this.toggleHeartRateMonitoring.bind(this)
      })

      // 步数统计卡片
      HealthCard({
        title: '今日步数',
        value: this.todaySteps,
        unit: '步',
        icon: $r('app.media.steps_icon'),
        showButton: false
      })

      // 健康数据图表
      HealthChart()
        .margin({ top: 20 })
    }
    .width('100%')
    .height('100%')
    .padding(16)
    .backgroundColor('#F5F5F5')
  }
}

@Component
struct HealthCard {
  private title: string = '';
  private value: number = 0;
  private unit: string = '';
  private icon: Resource = $r('app.media.default_icon');
  private isActive: boolean = false;
  private showButton: boolean = true;
  private onToggle?: () => void;

  build() {
    Column() {
      Row() {
        Image(this.icon)
          .width(24)
          .height(24)
          .margin({ right: 8 })
        
        Text(this.title)
          .fontSize(18)
          .fontWeight(FontWeight.Medium)
      }
      .justifyContent(FlexAlign.Start)
      .width('100%')
      .margin({ bottom: 12 })

      Row() {
        Text(this.value.toString())
          .fontSize(32)
          .fontWeight(FontWeight.Bold)
        
        Text(this.unit)
          .fontSize(16)
          .margin({ left: 4, top: 8 })
      }
      .justifyContent(FlexAlign.Center)
      .width('100%')

      if (this.showButton) {
        Button(this.isActive ? '停止监测' : '开始监测')
          .width('60%')
          .margin({ top: 16 })
          .onClick(() => {
            if (this.onToggle) {
              this.onToggle();
            }
          })
      }
    }
    .padding(16)
    .backgroundColor(Color.White)
    .borderRadius(12)
    .shadow({ radius: 6, color: '#00000020', offsetX: 0, offsetY: 2 })
    .margin({ bottom: 16 })
    .width('100%')
  }
}

5.2 健康数据图表组件

// src/main/ets/components/HealthChart.ets
@Component
export struct HealthChart {
  @State stepData: Array<{date: string, steps: number}> = [];
  private stepService: StepCounterService = StepCounterService.getInstance();

  aboutToAppear() {
    this.loadStepHistory();
  }

  async loadStepHistory() {
    this.stepData = await this.stepService.getStepHistory(7);
  }

  build() {
    Column() {
      Text('最近7天步数统计')
        .fontSize(18)
        .fontWeight(FontWeight.Medium)
        .margin({ bottom: 12 })
        .width('100%')
        .textAlign(TextAlign.Start)

      if (this.stepData.length > 0) {
        Row() {
          ForEach(this.stepData, (item) => {
            Column() {
              // 柱状图
              Column()
                .width(24)
                .height(item.steps / 50) // 按比例缩放高度
                .backgroundColor('#4CAF50')
                .borderRadius(4)
                .margin({ bottom: 4 })

              // 日期标签
              Text(item.date.split('-')[2]) // 只显示日
                .fontSize(12)
            }
            .margin({ right: 12 })
            .justifyContent(FlexAlign.End)
            .height(120)
          })
        }
        .width('100%')
        .justifyContent(FlexAlign.SpaceAround)
      } else {
        Text('暂无数据')
          .fontSize(14)
          .margin({ top: 20 })
      }
    }
    .padding(16)
    .backgroundColor(Color.White)
    .borderRadius(12)
    .shadow({ radius: 6, color: '#00000020', offsetX: 0, offsetY: 2 })
    .width('100%')
  }
}

六、数据持久化与云端同步

6.1 本地数据存储

// src/main/ets/service/StorageService.ets
import { dataStorage } from **********';

export class StorageService {
  private static instance: StorageService | null = null;
  private storage: dataStorage.Storage | null = null;

  private constructor() {
    this.initStorage();
  }

  public static getInstance(): StorageService {
    if (!StorageService.instance) {
      StorageService.instance = new StorageService();
    }
    return StorageService.instance;
  }

  private async initStorage(): Promise<void> {
    try {
      this.storage = await dataStorage.getStorage('/data/storage/health_data');
      console.info('Storage initialized successfully');
    } catch (error) {
      console.error(`Failed to initialize storage: ${JSON.stringify(error)}`);
    }
  }

  // 保存健康数据
  public async saveHealthData(key: string, value: any): Promise<boolean> {
    if (!this.storage) return false;

    try {
      await this.storage.put(key, JSON.stringify(value));
      await this.storage.flush();
      return true;
    } catch (error) {
      console.error(`Failed to save data: ${JSON.stringify(error)}`);
      return false;
    }
  }

  // 读取健康数据
  public async getHealthData(key: string): Promise<any> {
    if (!this.storage) return null;

    try {
      const value = await this.storage.get(key, '');
      return value ? JSON.parse(value) : null;
    } catch (error) {
      console.error(`Failed to get data: ${JSON.stringify(error)}`);
      return null;
    }
  }
}

6.2 云端数据同步

// src/main/ets/service/CloudSyncService.ets
import { cloud } from **********';
import { BusinessError } from **********';
import { StorageService } from './StorageService';

export class CloudSyncService {
  private static instance: CloudSyncService | null = null;
  private storageService: StorageService = StorageService.getInstance();

  private constructor() {}

  public static getInstance(): CloudSyncService {
    if (!CloudSyncService.instance) {
      CloudSyncService.instance = new CloudSyncService();
    }
    return CloudSyncService.instance;
  }

  // 同步健康数据到云端
  public async syncHealthData(userId: string): Promise<boolean> {
    try {
      // 从本地存储获取数据
      const heartData = await this.storageService.getHealthData('heart_rate_data');
      const stepData = await this.storageService.getHealthData('step_data');

      if (!heartData && !stepData) {
        console.info('No health data to sync');
        return true;
      }

      // 调用云端API同步数据
      const result = await cloud.callFunction({
        name: 'syncHealthData',
        data: {
          userId: userId,
          heartRate: heartData,
          steps: stepData
        }
      });

      if (result && result.success) {
        console.info('Health data synced successfully');
        return true;
      } else {
        console.error('Failed to sync health data');
        return false;
      }
    } catch (error) {
      const err: BusinessError = error as BusinessError;
      console.error(`Sync failed, code: ${err.code}, message: ${err.message}`);
      return false;
    }
  }

  // 从云端获取健康数据
  public async fetchHealthData(userId: string): Promise<boolean> {
    try {
      const result = await cloud.callFunction({
        name: 'getHealthData',
        data: {
          userId: userId
        }
      });

      if (result && result.data) {
        // 保存到本地存储
        await this.storageService.saveHealthData('heart_rate_data', result.data.heartRate);
        await this.storageService.saveHealthData('step_data', result.data.steps);
        return true;
      }
      return false;
    } catch (error) {
      console.error(`Fetch health data failed: ${JSON.stringify(error)}`);
      return false;
    }
  }
}

七、应用测试与优化

7.1 功能测试要点

  1. 心率监测测试:验证实时心率数据准确性测试开始/停止监测功能验证无权限时的错误处理
  2. 步数统计测试:验证步数统计准确性测试历史数据查询功能验证跨日期数据分割
  3. 数据同步测试:测试本地数据存储验证云端同步功能测试网络异常处理

7.2 性能优化建议

  1. 数据采集优化:
  2. 内存管理优化:及时释放不再使用的资源使用@State和@Link管理组件状态避免不必要的全局变量
  3. UI渲染优化:使用ForEach渲染列表数据对复杂计算使用Worker线程减少不必要的组件重建

八、项目扩展方向

  1. 健康趋势分析:增加周/月健康数据分析提供健康评分系统生成健康报告
  2. 智能提醒功能:久坐提醒心率异常提醒每日目标达成提醒
  3. 社交功能:健康数据分享好友健康挑战健康社区互动
  4. 设备扩展:支持更多健康设备实现多设备数据同步开发手表配套应用

九、总结

本教程详细介绍了如何使用DevEco Studio和ArkTS开发基于HarmonyOS Next的健康监测应用。通过本项目的实践,开发者可以掌握:

  1. 鸿蒙健康数据API的使用方法
  2. ArkTS语言的核心特性
  3. 鸿蒙应用的UI开发技巧
  4. 数据持久化与云端同步方案
  5. 性能优化与测试方法

健康管理类应用是鸿蒙生态中的重要组成部分,随着HarmonyOS Next的不断发展,开发者可以充分利用其分布式能力和丰富的硬件生态,创造出更加智能、个性化的健康管理解决方案。

全部评论

相关推荐

本🐭的第一场面试,结果上来就是重量级,这个时候很多概念都不清晰,很多简单题回答不好,面完后狠狠复盘了一番~写了一些问题总结。1.背景介绍:自我介绍2.怎么看待前端可能被AI取代3.为什么学Vue没有学React技术问题:4.Vue的响应式是怎么实现的5.Vue组件是怎么通信的6.用pinia做全局的状态管理,跟直接写这种组件,比如说vue自带的一些状态的管理,他们的区别是什么7.为什么我们要用Promise-&nbsp;追问:那你对这种异步编程是怎么理解的?为什么我们需要这种异步编程?它异步在哪里?8.那你觉得Promise和回调函数的区别是什么-&nbsp;追问:比如说你请求一个接口,请求完之后可以传一个回调函数进去,让他接着处理后面那些逻辑。而Promis使用.then.catch这种方法。你觉得promise这种方法和回调函数直接把逻辑传进去最大的区别是什么9.讲讲XHR、fetch、axios的区别讲XHR是底层、fetch是更新用法、axios是封装-&nbsp;追问:axios还有一个很明显的跟另外两个的区别-&nbsp;没有回答出来,我说axios是基于promise的,面试官说fetch也是…-&nbsp;正确答案:axios是浏览器和服务端都能用,fetch和XHR主要是在浏览器上用10.刚刚提到HTTP,那HTTP可以大概介绍一下吗?回答了TCP相关内容……面试官说http是基于TCP的封装,有自己的一套东西,比如说get、post这些http方法,比如说header这样的一个请求头的组成这些11.讲讲CSS的盒模型一顿输出,跑题了。12.讲讲inline-bolck和block的区别回答内部展示inline特性,外部展示block特性,宽高都可以设置。问宽度也可以设置?回答可以。13.flex和bolck的区别甚至忘记了“弹性盒”这个词,还在说什么主轴、纵轴14.CSS选择器有哪些?优先级是怎么样的?同时使用很多个选择器,优先级是怎么判断的15.JS有哪些数据类型?这些数据类型怎么在代码中进行判断?16.判断类型有哪些方法?17.es6相比es5多了有哪些语法18.箭头函数和普通函数的区别是什么回答1书写方式&nbsp;2this指向-&nbsp;19追问:似乎想让我说的是其他部分,难受我操的-&nbsp;20追问:如果想改变普通函数的this指向,有哪些方法:call,apply,bind,有什么不同,bind的用法忘了-&nbsp;21追问:箭头函数和普通函数还有一个比较重要的区别:没答上来,答案是箭头函数不能用作构造函数(忘记了。。。)22.常用的数组的方法有哪些?手撕:两数相加&nbsp;梦的起点leetcode&nbsp;1用对象(object/set)的话,可以把值当作key,先循环一次把数据存到对象里,key和value可以设成一样的值。然后再遍历一次,用目标值减去当前值,比如把2、7、11、15存到对象里后,遍历的时候计算9&nbsp;-&nbsp;2是否等于7,再去对象里用这个差值作为key查找,就能知道结果了。反问改进:因为你学习前端的时间比较短,目前很多概念理解得还不够深入,有些都混淆了。而且从刚刚答题情况看,你JS代码写得可能也比较少,像刚才那道题,JS写得多的话应该很快就能想到用对象来优化。所以我建议你先沉淀一下,不要着急练习项目。现在才刚起步,要把基础打牢。另外,我不太建议你现在学Vue,更推荐学react。像字节跳动,整个集团都主要用react开发,而快手、美团这些公司可能Vue用得更多,你可以根据自己未来想去的公司来选择。还有HTTP相关知识,和TCP的区别比较大,你要再多看看。剩下的就是建议你多看书,系统地学习,很多概念你还没抓住重点,回答问题时有时没讲到关键内容,却回答了一些边缘问题,之后可以复盘总结一下。
查看24道真题和解析
点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客企业服务