鸿蒙跨设备健康助手设计与实现
基于HarmonyOS的分布式能力和传感器框架,我们设计了一个跨设备同步的健康助手应用,能够实时记录步数、同步健康数据并生成可视化图表。Canvas。
·
鸿蒙跨设备健康助手设计与实现
一、系统架构设计
基于HarmonyOS的分布式能力和传感器框架,我们设计了一个跨设备同步的健康助手应用,能够实时记录步数、同步健康数据并生成可视化图表。
https://example.com/health-assistant-arch.png
系统包含四大核心模块:
- 传感器模块 - 使用
@ohos.sensor获取步数数据 - 数据存储模块 - 使用
@ohos.data.storage本地持久化 - 分布式同步模块 - 通过
@ohos.distributedData实现多设备数据同步 - 可视化模块 - 使用
Canvas组件绘制统计图表
二、核心代码实现
1. 步数传感器服务(ArkTS)
// StepCounterService.ets
import sensor from '@ohos.sensor';
import distributedData from '@ohos.distributedData';
const STEP_COUNTER_SYNC_CHANNEL = 'step_counter_sync';
class StepCounterService {
private static instance: StepCounterService = null;
private dataManager: distributedData.DataManager;
private stepCount: number = 0;
private lastUpdateTime: number = 0;
private listeners: StepListener[] = [];
private constructor() {
this.initDataManager();
this.initStepCounter();
}
public static getInstance(): StepCounterService {
if (!StepCounterService.instance) {
StepCounterService.instance = new StepCounterService();
}
return StepCounterService.instance;
}
private initDataManager() {
this.dataManager = distributedData.createDataManager({
bundleName: 'com.example.healthassistant',
area: distributedData.Area.GLOBAL
});
this.dataManager.registerDataListener(STEP_COUNTER_SYNC_CHANNEL, (data) => {
this.handleSyncData(data);
});
}
private initStepCounter() {
try {
sensor.on(sensor.SensorId.STEP_COUNTER, (data) => {
this.handleStepData(data);
});
console.info('步数传感器初始化成功');
} catch (err) {
console.error('步数传感器初始化失败:', JSON.stringify(err));
}
}
private handleStepData(data: sensor.SensorResponse) {
const now = Date.now();
const steps = data.steps;
// 计算新增步数
const newSteps = this.lastUpdateTime > 0 ?
steps - this.stepCount :
steps;
this.stepCount = steps;
this.lastUpdateTime = now;
// 通知监听器
this.listeners.forEach(listener => {
listener.onStepChanged(newSteps, this.stepCount);
});
// 同步到其他设备
this.syncStepData({
timestamp: now,
totalSteps: this.stepCount,
deviceId: this.getLocalDeviceId()
});
}
private syncStepData(stepData: StepData) {
this.dataManager.syncData(STEP_COUNTER_SYNC_CHANNEL, {
type: 'stepUpdate',
data: stepData
});
}
private handleSyncData(data: any) {
if (data?.type === 'stepUpdate' && data.data) {
const stepData: StepData = data.data;
// 忽略自己设备同步的数据
if (stepData.deviceId === this.getLocalDeviceId()) {
return;
}
// 更新步数(简单合并,实际应用可能需要更复杂的逻辑)
this.stepCount = Math.max(this.stepCount, stepData.totalSteps);
this.lastUpdateTime = stepData.timestamp;
this.listeners.forEach(listener => {
listener.onStepChanged(0, this.stepCount); // 不触发新增步数通知
});
}
}
public getCurrentSteps(): number {
return this.stepCount;
}
public addStepListener(listener: StepListener) {
if (!this.listeners.includes(listener)) {
this.listeners.push(listener);
}
}
public removeStepListener(listener: StepListener) {
this.listeners = this.listeners.filter(l => l !== listener);
}
private getLocalDeviceId(): string {
// 实际实现需要获取设备ID
return 'local_device';
}
}
interface StepListener {
onStepChanged(newSteps: number, totalSteps: number): void;
}
interface StepData {
timestamp: number;
totalSteps: number;
deviceId: string;
}
export const stepCounterService = StepCounterService.getInstance();
2. 健康数据存储服务(ArkTS)
// HealthDataService.ets
import dataStorage from '@ohos.data.storage';
import distributedData from '@ohos.distributedData';
const HEALTH_DATA_SYNC_CHANNEL = 'health_data_sync';
const STORAGE_FILE = 'health_data';
class HealthDataService {
private static instance: HealthDataService = null;
private storage: dataStorage.Storage;
private dataManager: distributedData.DataManager;
private constructor() {
this.initStorage();
this.initDataManager();
}
public static getInstance(): HealthDataService {
if (!HealthDataService.instance) {
HealthDataService.instance = new HealthDataService();
}
return HealthDataService.instance;
}
private async initStorage() {
try {
this.storage = await dataStorage.getStorage(STORAGE_FILE);
console.info('健康数据存储初始化成功');
} catch (err) {
console.error('健康数据存储初始化失败:', JSON.stringify(err));
}
}
private initDataManager() {
this.dataManager = distributedData.createDataManager({
bundleName: 'com.example.healthassistant',
area: distributedData.Area.GLOBAL
});
this.dataManager.registerDataListener(HEALTH_DATA_SYNC_CHANNEL, (data) => {
this.handleSyncData(data);
});
}
public async saveDailyData(date: string, data: DailyHealthData): Promise<void> {
if (!this.storage) return;
try {
await this.storage.put(date, JSON.stringify(data));
await this.storage.flush();
// 同步到其他设备
this.syncHealthData({
date: date,
data: data
});
} catch (err) {
console.error('保存健康数据失败:', JSON.stringify(err));
}
}
public async getDailyData(date: string): Promise<DailyHealthData | null> {
if (!this.storage) return null;
try {
const data = await this.storage.get(date, '');
return data ? JSON.parse(data) : null;
} catch (err) {
console.error('获取健康数据失败:', JSON.stringify(err));
return null;
}
}
public async getAllDates(): Promise<string[]> {
if (!this.storage) return [];
try {
return await this.storage.listAllKeys();
} catch (err) {
console.error('获取日期列表失败:', JSON.stringify(err));
return [];
}
}
private syncHealthData(healthData: SyncHealthData) {
this.dataManager.syncData(HEALTH_DATA_SYNC_CHANNEL, {
type: 'healthDataUpdate',
data: healthData
});
}
private handleSyncData(data: any) {
if (data?.type === 'healthDataUpdate' && data.data) {
const syncData: SyncHealthData = data.data;
// 保存同步过来的数据
this.saveDailyData(syncData.date, syncData.data).catch(err => {
console.error('同步保存健康数据失败:', JSON.stringify(err));
});
}
}
}
interface DailyHealthData {
steps: number;
distance: number; // 米
calories: number; // 千卡
heartRate?: number; // 可选心率数据
}
interface SyncHealthData {
date: string;
data: DailyHealthData;
}
export const healthDataService = HealthDataService.getInstance();
3. 健康数据可视化组件(ArkUI)
// HealthChart.ets
@Component
export struct HealthChart {
@State chartData: ChartData[] = [];
@State selectedDate: string = '';
private stepService = stepCounterService;
private healthService = healthDataService;
aboutToAppear() {
this.loadChartData();
// 监听步数变化
this.stepService.addStepListener({
onStepChanged: (newSteps, totalSteps) => {
this.updateTodaySteps(totalSteps);
}
});
}
aboutToDisappear() {
this.stepService.removeStepListener({
onStepChanged: (newSteps, totalSteps) => {
this.updateTodaySteps(totalSteps);
}
});
}
build() {
Column() {
// 日期选择器
DatePicker({
start: '2020-01-01',
end: '2100-12-31',
selected: this.selectedDate || new Date().toISOString().split('T')[0]
})
.onChange((value: DatePickerResult) => {
this.selectedDate = `${value.year}-${value.month}-${value.day}`;
this.loadChartData();
})
// 图表容器
Stack() {
// 背景网格
this.buildGrid()
// 柱状图
ForEach(this.chartData, (item) => {
this.buildBar(item)
})
// 标签
this.buildLabels()
}
.height(300)
.margin({ top: 20 })
}
}
@Builder
buildGrid() {
// 水平网格线
ForEach([0, 1, 2, 3, 4], (i) => {
Line()
.width('100%')
.height(1)
.backgroundColor('#E0E0E0')
.position({ x: 0, y: i * 60 })
})
}
@Builder
buildBar(item: ChartData) {
const barHeight = item.value / this.getMaxValue() * 240;
Column() {
// 柱状条
Column()
.width(30)
.height(barHeight)
.backgroundColor('#4CAF50')
.borderRadius(4)
// 数据标签
Text(item.value.toString())
.fontSize(12)
.margin({ top: 4 })
}
.position({ x: item.position * 80 + 40, y: 240 - barHeight })
}
@Builder
buildLabels() {
// X轴标签
ForEach(this.chartData, (item) => {
Text(item.label)
.fontSize(12)
.position({ x: item.position * 80 + 40, y: 250 })
})
}
private async loadChartData() {
const date = this.selectedDate || new Date().toISOString().split('T')[0];
const data = await this.healthService.getDailyData(date);
if (data) {
this.chartData = [
{ label: '步数', value: data.steps, position: 0 },
{ label: '距离', value: data.distance, position: 1 },
{ label: '卡路里', value: data.calories, position: 2 }
];
} else {
// 默认数据
this.chartData = [
{ label: '步数', value: 0, position: 0 },
{ label: '距离', value: 0, position: 1 },
{ label: '卡路里', value: 0, position: 2 }
];
}
}
private async updateTodaySteps(steps: number) {
const today = new Date().toISOString().split('T')[0];
if (this.selectedDate === today) {
const data = await this.healthService.getDailyData(today) || {
steps: 0,
distance: 0,
calories: 0
};
data.steps = steps;
data.distance = this.calculateDistance(steps);
data.calories = this.calculateCalories(steps);
this.chartData = [
{ label: '步数', value: data.steps, position: 0 },
{ label: '距离', value: data.distance, position: 1 },
{ label: '卡路里', value: data.calories, position: 2 }
];
// 保存更新后的数据
await this.healthService.saveDailyData(today, data);
}
}
private calculateDistance(steps: number): number {
// 假设步长0.7米
return steps * 0.7;
}
private calculateCalories(steps: number): number {
// 简单计算:0.04千卡/步
return steps * 0.04;
}
private getMaxValue(): number {
return Math.max(...this.chartData.map(item => item.value), 1);
}
}
interface ChartData {
label: string;
value: number;
position: number;
}
三、主界面整合
1. 健康助手主页面
// MainPage.ets
import { stepCounterService } from './StepCounterService';
import { healthDataService } from './HealthDataService';
@Entry
@Component
struct MainPage {
@State currentSteps: number = 0;
@State todayData: DailyHealthData | null = null;
@State deviceList: DeviceInfo[] = [];
private stepListener: StepListener = {
onStepChanged: (newSteps, totalSteps) => {
this.currentSteps = totalSteps;
this.updateTodayData(totalSteps);
}
};
aboutToAppear() {
// 初始化步数监听
stepCounterService.addStepListener(this.stepListener);
this.currentSteps = stepCounterService.getCurrentSteps();
// 加载今日数据
this.loadTodayData();
// 加载设备列表
this.loadDeviceList();
}
aboutToDisappear() {
stepCounterService.removeStepListener(this.stepListener);
}
build() {
Column() {
// 头部状态
this.buildHeader()
// 步数卡片
this.buildStepCard()
// 健康图表
HealthChart()
.margin({ top: 20 })
// 设备列表
this.buildDeviceList()
}
.padding(20)
}
@Builder
buildHeader() {
Row() {
Text('健康助手')
.fontSize(24)
.fontWeight(FontWeight.Bold)
Button('同步')
.onClick(() => {
this.syncAllData();
})
}
.width('100%')
.justifyContent(FlexAlign.SpaceBetween)
}
@Builder
buildStepCard() {
Column() {
Text('今日步数')
.fontSize(16)
Text(this.currentSteps.toString())
.fontSize(36)
.fontWeight(FontWeight.Bold)
.margin({ top: 8 })
if (this.todayData) {
Row() {
Text(`距离: ${this.todayData.distance.toFixed(1)}米`)
Text(`卡路里: ${this.todayData.calories.toFixed(1)}千卡`)
.margin({ left: 20 })
}
.margin({ top: 12 })
}
}
.width('100%')
.padding(20)
.backgroundColor('#FFFFFF')
.borderRadius(12)
}
@Builder
buildDeviceList() {
Column() {
Text('已连接设备')
.fontSize(18)
.margin({ bottom: 10 })
ForEach(this.deviceList, (device) => {
Row() {
Image(device.icon)
.width(40)
.height(40)
Column() {
Text(device.name)
Text(`步数: ${device.steps}`)
.fontSize(12)
.margin({ top: 4 })
}
.margin({ left: 10 })
}
.width('100%')
.padding(10)
.backgroundColor('#F5F5F5')
.borderRadius(8)
.margin({ bottom: 8 })
})
}
.margin({ top: 20 })
}
private async loadTodayData() {
const today = new Date().toISOString().split('T')[0];
this.todayData = await healthDataService.getDailyData(today);
if (!this.todayData) {
this.todayData = {
steps: this.currentSteps,
distance: this.calculateDistance(this.currentSteps),
calories: this.calculateCalories(this.currentSteps)
};
await healthDataService.saveDailyData(today, this.todayData);
}
}
private async updateTodayData(steps: number) {
const today = new Date().toISOString().split('T')[0];
this.todayData = {
steps: steps,
distance: this.calculateDistance(steps),
calories: this.calculateCalories(steps)
};
await healthDataService.saveDailyData(today, this.todayData);
}
private calculateDistance(steps: number): number {
return steps * 0.7;
}
private calculateCalories(steps: number): number {
return steps * 0.04;
}
private async loadDeviceList() {
// 模拟设备数据
this.deviceList = [
{ id: '1', name: '我的手机', steps: 8520, icon: $r('app.media.phone') },
{ id: '2', name: '智能手表', steps: 7680, icon: $r('app.media.watch') }
];
}
private syncAllData() {
// 同步今日数据
const today = new Date().toISOString().split('T')[0];
if (this.todayData) {
healthDataService.saveDailyData(today, this.todayData);
}
// 同步步数
stepCounterService.syncStepData({
timestamp: Date.now(),
totalSteps: this.currentSteps,
deviceId: 'local_device'
});
promptAction.showToast({
message: '数据已同步',
duration: 2000
});
}
}
interface DeviceInfo {
id: string;
name: string;
steps: number;
icon: Resource;
}
四、权限配置与模块声明
1. 配置文件
// module.json5
{
"module": {
"requestPermissions": [
{
"name": "ohos.permission.ACTIVITY_MOTION",
"reason": "访问步数传感器"
},
{
"name": "ohos.permission.DISTRIBUTED_DATASYNC",
"reason": "跨设备数据同步"
},
{
"name": "ohos.permission.HEALTH_DATA",
"reason": "访问健康数据"
}
],
"abilities": [
{
"name": "MainAbility",
"type": "page",
"visible": true
}
],
"distributedNotification": {
"scenarios": [
{
"name": "health_data_sync",
"value": "health_assistant"
}
]
}
}
}
2. 资源文件
<!-- resources/base/element/string.json -->
{
"string": [
{
"name": "app_name",
"value": "健康助手"
},
{
"name": "step_counter_title",
"value": "今日步数"
},
{
"name": "distance_label",
"value": "距离"
},
{
"name": "calories_label",
"value": "卡路里"
}
]
}
五、跨设备同步优化
1. 增量同步策略
// EnhancedHealthSync.ets
class EnhancedHealthSync {
private lastSyncTime: number = 0;
async syncIncrementalData() {
const now = Date.now();
const lastSync = this.lastSyncTime;
this.lastSyncTime = now;
// 获取需要同步的日期范围
const dates = await this.getDatesToSync(lastSync);
// 分批同步
const batchSize = 5;
for (let i = 0; i < dates.length; i += batchSize) {
const batch = dates.slice(i, i + batchSize);
await this.syncBatchData(batch);
}
}
private async getDatesToSync(lastSync: number): Promise<string[]> {
const allDates = await healthDataService.getAllDates();
if (lastSync === 0) {
return allDates; // 首次同步全部数据
}
// 只同步修改时间晚于上次同步时间的数据
const datesToSync: string[] = [];
for (const date of allDates) {
const data = await healthDataService.getDailyData(date);
if (data && data.timestamp && data.timestamp > lastSync) {
datesToSync.push(date);
}
}
return datesToSync;
}
private async syncBatchData(dates: string[]) {
const batchData: SyncHealthData[] = [];
for (const date of dates) {
const data = await healthDataService.getDailyData(date);
if (data) {
batchData.push({
date: date,
data: data,
timestamp: Date.now()
});
}
}
if (batchData.length > 0) {
distributedData.syncData(HEALTH_DATA_SYNC_CHANNEL, {
type: 'batchHealthData',
data: batchData
});
}
}
}
2. 冲突解决策略
// HealthDataConflictResolver.ets
class HealthDataConflictResolver {
async resolveConflict(localData: DailyHealthData, remoteData: DailyHealthData): Promise<DailyHealthData> {
// 简单策略:取各指标的最大值
return {
steps: Math.max(localData.steps, remoteData.steps),
distance: Math.max(localData.distance, remoteData.distance),
calories: Math.max(localData.calories, remoteData.calories),
heartRate: this.resolveHeartRate(localData.heartRate, remoteData.heartRate),
timestamp: Date.now()
};
}
private resolveHeartRate(local?: number, remote?: number): number | undefined {
if (local === undefined) return remote;
if (remote === undefined) return local;
// 取最近的数据
return local; // 实际实现可能需要更复杂的逻辑
}
}
六、项目扩展方向
- 多健康指标支持:增加心率、睡眠等数据监测
- 目标设置与提醒:设置每日步数目标并提醒
- 社交功能:与好友分享健康数据
- 健康建议:基于数据分析提供个性化建议
- 多设备协同:智能分配数据采集任务
// 扩展示例:多指标支持
interface ExtendedHealthData extends DailyHealthData {
sleepHours?: number;
bloodPressure?: {
systolic: number;
diastolic: number;
};
bloodOxygen?: number;
}
// 扩展示例:目标提醒
class GoalReminder {
private dailyGoal: number = 10000;
checkGoal(steps: number) {
if (steps >= this.dailyGoal) {
notification.publish({
content: {
title: '目标达成',
text: `恭喜您完成了今日${this.dailyGoal}步的目标!`
}
});
} else if (steps >= this.dailyGoal * 0.8) {
notification.publish({
content: {
title: '目标接近',
text: `您已完成${steps}步,距离目标还有${this.dailyGoal - steps}步`
}
});
}
}
}
通过以上实现,我们创建了一个功能完善的跨设备健康助手应用,能够实时监测步数、同步健康数据并提供可视化分析。系统充分利用了HarmonyOS的分布式能力,确保用户在不同设备上获得一致的健康数据体验。
更多推荐

所有评论(0)