基于鸿蒙os开发跑步管理系统(二)-架构和技术栈
架构与技术栈
整体架构
RunAdvisor 采用单 Ability 单页面的架构设计,通过 NavPathStack 导航栈管理页面跳转,Tabs 组件承载五个功能模块,所有业务逻辑集中在 Index.ets 中通过 @Builder 方法分模块组织。
架构图
+------------------------------------------------------------------+
| HarmonyOS 6.1.1 |
+------------------------------------------------------------------+
| |
| EntryAbility |
| +------------------------------------------------------------+ |
| | onCreate(): | |
| | - PreferencesService.init(context) | |
| | - LocationEnhanceService.startGyroscope() | |
| +------------------------------------------------------------+ |
| | |
| v |
| Index (@Entry @Component) |
| +------------------------------------------------------------+ |
| | Navigation(NavPathStack) | |
| | +------------------------------------------------------+ | |
| | | Tabs (5 TabContent) | | |
| | | |-- HomePage (天气/步数/热量/建议) | | |
| | | |-- DietPage (食物列表/搜索/摄入记录) | | |
| | | |-- AdvicePage (跑步建议详情) | | |
| | | |-- RoutePage (路线列表) | | |
| | | |-- ProfilePage (个人中心) | | |
| | +------------------------------------------------------+ | |
| | | |
| | NavDestination: | |
| | |-- ProfileSetupPage (个人资料设置/编辑) | |
| | |-- MapRoutePageNav (路线地图+详情) | |
| +------------------------------------------------------------+ |
| | |
| +-------------------+-------------------+ |
| | | | |
| v v v |
| +-------------+ +---------------+ +-----------------+ |
| | Model 层 | | Service 层 | | Constant 层 | |
| | - UserProfile| | - Preferences | | - Constants | |
| | - WeatherInfo| | - Calorie | | (45+ 常量) | |
| | - FoodItem | | - Weather | +-----------------+ |
| | - DietRecord | | - Step | |
| | - RunAdvice | | - Location | |
| | - RouteInfo | | - RunAdvisor | |
| | - RoutePoint | | - Route | |
| | - Landmark | | - LocationEnh | |
| +-------------+ +---------------+ |
| |
+------------------------------------------------------------------+
| | | |
v v v v
+------------+ +------------+ +-----------+ +-----------+
| @kit. | | @kit. | | @kit. | | @kit. |
| ArkData | | NetworkKit | | Location | | Sensor |
| (Prefs) | | (HTTP) | | Kit | | ServiceKit|
+------------+ +------------+ +-----------+ +-----------+
技术栈详解
HarmonyOS SDK
| Kit | 用途 | 关键 API |
|---|---|---|
| @kit.ArkData | 本地数据持久化 | preferences.getPreferencesSync, putSync, getSync, flushSync |
| @kit.NetworkKit | 网络请求 | http.createHttp, request, RequestMethod.GET |
| @kit.LocationKit | 定位服务 | geoLocationManager.getCurrentLocation |
| @kit.SensorServiceKit | 传感器 | sensor.once(PEDOMETER), sensor.on/off(GYROSCOPE) |
| @kit.AbilityKit | 应用框架 | UIAbility, Want, AbilityConstant |
| @kit.ArkUI | UI 框架 | Navigation, Tabs, Canvas, Progress, ForEach |
| @kit.BasicServicesKit | 基础服务 | BusinessError |
| @kit.PerformanceAnalysisKit | 日志 | hilog.info, hilog.error, hilog.warn |
ArkTS 严格模式约束
本项目遵循 ArkTS 严格模式规范,主要约束包括:
- 禁止使用 any 类型,所有变量需明确类型声明
- 禁止使用动态属性访问,使用 Record<string, Object> 替代
- 使用静态工厂方法(static create)代替构造器参数
- 禁止使用 standalone this,所有 @Builder 方法需绑定组件实例
- 使用 ForEach 替代 map/filter 等函数式操作
项目目录结构
RunAdvisor/
|-- AppScope/
| |-- app.json5 # 应用全局配置
|-- entry/
| |-- src/main/
| | |-- ets/
| | | |-- entryability/
| | | | |-- EntryAbility.ets # 应用入口 Ability
| | | |-- entrybackupability/
| | | | |-- EntryBackupAbility.ets # 备份扩展
| | | |-- pages/
| | | | |-- Index.ets # 主页面 (约1300行)
| | | |-- model/
| | | | |-- UserProfile.ets # 用户档案模型
| | | | |-- WeatherInfo.ets # 天气信息模型
| | | | |-- FoodItem.ets # 食物/饮食记录模型
| | | | |-- RunAdvice.ets # 跑步建议模型
| | | | |-- RouteInfo.ets # 路线/途经点/地标模型
| | | |-- service/
| | | | |-- PreferencesService.ets # 偏好存储服务
| | | | |-- CalorieService.ets # 热量数据服务
| | | | |-- WeatherService.ets # 天气查询服务
| | | | |-- StepService.ets # 计步器服务
| | | | |-- LocationService.ets # 定位服务
| | | | |-- RunAdvisorService.ets # 跑步建议算法服务
| | | | |-- RouteService.ets # 路线数据服务
| | | | |-- LocationEnhanceService.ets # 位置增强服务
| | | |-- constant/
| | | |-- Constants.ets # 全局常量定义
| | |-- resources/ # 资源文件
| | |-- module.json5 # 模块配置与权限声明
| |-- build-profile.json5 # 模块构建配置
|-- docs/ # 项目文档
|-- build-profile.json5 # 项目构建配置
|-- hvigorfile.ts # 构建脚本
|-- oh-package.json5 # 依赖管理
权限声明 (module.json5)
应用在 module.json5 的 requestPermissions 中声明了以下五个权限:
| 权限 | 用途 | 使用场景 |
|---|---|---|
| ohos.permission.INTERNET | 访问网络,请求天气 API | 运行时 (inuse) |
| ohos.permission.APPROXIMATELY_LOCATION | 粗略定位,获取城市级位置 | 运行时 (inuse) |
| ohos.permission.LOCATION | 精确定位,获取 GPS 坐标 | 运行时 (inuse) |
| ohos.permission.ACTIVITY_MOTION | 计步器传感器,读取步数 | 运行时 (inuse) |
| ohos.permission.GYROSCOPE | 陀螺仪传感器,位置增强 | 运行时 (inuse) |
权限声明代码片段:
"requestPermissions": [
{
"name": "ohos.permission.INTERNET",
"reason": "$string:perm_internet_reason",
"usedScene": { "abilities": ["EntryAbility"], "when": "inuse" }
},
{
"name": "ohos.permission.APPROXIMATELY_LOCATION",
"reason": "$string:perm_location_reason",
"usedScene": { "abilities": ["EntryAbility"], "when": "inuse" }
},
{
"name": "ohos.permission.LOCATION",
"reason": "$string:perm_location_reason",
"usedScene": { "abilities": ["EntryAbility"], "when": "inuse" }
},
{
"name": "ohos.permission.ACTIVITY_MOTION",
"reason": "$string:perm_step_reason",
"usedScene": { "abilities": ["EntryAbility"], "when": "inuse" }
},
{
"name": "ohos.permission.GYROSCOPE",
"reason": "$string:perm_gyro_reason",
"usedScene": { "abilities": ["EntryAbility"], "when": "inuse" }
}
]
导航架构
应用采用 Navigation + NavPathStack 的栈式导航模式:
Navigation (mode: Stack)
|
+-- Tabs (主内容区,5个标签页)
|
+-- NavDestination: ProfileSetup
| 首次启动引导或编辑个人资料
|
+-- NavDestination: MapRoute
路线地图 Canvas 渲染 + 详情信息
导航触发时机
| 源页面 | 目标页面 | 触发条件 |
|---|---|---|
| Index (首次启动) | ProfileSetup | isProfileSet === false |
| ProfilePage | ProfileSetup | 点击"编辑资料"按钮 |
| AdvicePage | MapRoute | 点击"查看路线"按钮 |
| RoutePage | MapRoute | 点击路线卡片 |
NavDestination 路由注册
@Builder
NavDestBuilder(name: string, param: Object) {
if (name === 'ProfileSetup') {
this.ProfileSetupPage()
} else if (name === 'MapRoute') {
this.MapRoutePageNav(param as string)
}
}
MapRoute 页面通过 param 传递路线类型(easy/standard/endurance),实现同一页面展示不同路线数据。
设计模式
单例模式
所有 Service 类均采用单例模式,确保全局唯一实例:
private static instance: XxxService | undefined = undefined;
private constructor() {}
static getInstance(): XxxService {
if (XxxService.instance === undefined) {
XxxService.instance = new XxxService();
}
return XxxService.instance;
}
静态工厂方法
所有 Model 类采用静态工厂方法创建实例,避免构造器参数:
static create(weight: number, height: number, ...): UserProfile {
const p = new UserProfile();
p.weight = weight;
p.height = height;
...
return p;
}
架构设计决策
为什么选择单 Ability 架构
HarmonyOS 支持多 Ability 架构,每个 Ability 拥有独立的 UI 上下文和生命周期。RunAdvisor 选择单 Ability 设计的原因如下:
第一,应用功能高度内聚。首页、饮食、建议、路线和个人中心五个模块共享同一套用户数据(UserProfile、DietRecord、WeatherInfo),单 Ability 避免了跨 Ability 数据传递的复杂性。
第二,ArkTS 严格模式下,@State 变量的作用域限定在 @Component 内部。如果采用多 Ability,每个 Ability 需要独立维护状态副本,数据同步将引入大量偏好读写操作,增加延迟和出错风险。
第三,NavPathStack 提供了完善的栈式导航能力,足以满足本应用的页面跳转需求(首页到设置页、建议页到地图页),无需引入额外的 Ability 切换开销。
为什么选择单文件集中式布局
Index.ets 约 1300 行,集中了所有 UI 构建 @Builder 方法。这一选择并非没有考虑过拆分,而是基于以下权衡:
ArkTS 严格模式禁止 standalone this,@Builder 方法如果定义在其他 @Component 中,调用时必须通过 bind 绑定上下文,代码反而更复杂。将所有 @Builder 定义在同一个 @Entry @Component 中,this 引用自然绑定,状态变量共享无需额外处理。
此外,ArkTS 不支持跨文件 @Builder 引用(除非通过 @Component 封装),将页面拆分为独立文件需要为每个页面创建独立的 @Component,增加组件间通信成本。单文件方案下,所有 @State 变量集中在顶层组件,子构建器通过 this 直接访问,数据流清晰。
为什么不用 MapComponent 而用 Canvas
HarmonyOS 提供了 MapComponent 组件用于地图展示,但 RunAdvisor 选择 Canvas 动态绘制路线地图,原因有三:
第一,MapComponent 依赖第三方地图服务(如百度地图、高德地图),需要申请 API 密钥并在应用中配置,增加发布复杂度。Canvas 方案完全本地化,无需外部依赖。
第二,MapComponent 加载地图瓦片需要持续的网络连接,而 Canvas 方案使用预置的 GPS 坐标数据,完全离线可用,适合跑步场景(户外网络信号不稳定)。
第三,Canvas 提供了完全的绘制控制权,可以自定义路线样式、途经点标注、地标图标等视觉元素,不受地图 SDK 样式限制。
为什么使用 Preferences 而非关系型数据库
HarmonyOS 提供了关系型数据库 (RDB) 和轻量级偏好存储 (Preferences) 两种本地数据方案。RunAdvisor 选择 Preferences 的原因:
数据模型简单。UserProfile 是单条记录,DietRecord 是追加写入的列表,StepCount 是单值。这些场景更适合 KV 存储,无需 SQL 查询的灵活性。
操作便捷。Preferences 的 getSync/putSync 同步 API 简单直接,无需处理异步回调或游标迭代。在应用启动时同步读取数据,确保 UI 初始化时数据已就绪。
性能足够。本应用数据量极小(用户档案几十字节、饮食记录几十条),Preferences 的性能完全满足需求。RDB 的优势在大数据量查询,对本应用无意义。
ArkTS 严格模式影响分析
ArkTS 严格模式对项目代码产生了深远影响,以下逐条分析关键约束及应对策略:
禁止 any 类型
所有变量必须声明明确类型。在解析 JSON 响应时,不能使用 any 接收,必须通过 Record<string, Object> 中转,再逐字段类型断言:
const obj: Record<string, Object> = JSON.parse(json) as Record<string, Object>;
const temp: number = obj['temp'] as number;
这一约束虽然增加了代码量,但消除了运行时类型错误的风险。
禁止动态属性访问
不允许 obj[key] 的 key 为变量的写法(当 obj 类型为具体类时)。解决方案:对于 JSON 解析场景使用 Record<string, Object>;对于已知结构使用静态字段访问。
静态工厂方法替代构造器参数
ArkTS 严格模式下,类的构造器不能带参数(@Sendable 类限制)。所有 Model 类采用无参构造器 + 静态工厂方法模式:
export class UserProfile {
weight: number = 70; // 默认值必须初始化
private constructor() {}
static create(weight: number, ...): UserProfile {
const p = new UserProfile();
p.weight = weight;
return p;
}
}
ForEach 替代 map/filter
ArkTS 不支持在 @Builder 上下文中使用 map、filter 等数组高阶方法。渲染列表必须使用 ForEach:
ForEach(dataSource, (item: Item) => {
Text(item.name)
}, (item: Item) => item.id)
ForEach 的第三个参数(键值生成器)必须提供,确保列表更新时的高效 diff 计算。
服务初始化顺序的重要性
服务初始化顺序在 aboutToAppear() 中严格遵循依赖关系:
- PreferencesService 必须最先初始化(在 EntryAbility.onCreate 中完成),因为其他服务(WeatherService 缓存、StepService 步数存储)依赖它。
- LocationEnhanceService.startGyroscope() 在 onCreate 中调用,确保陀螺仪数据在页面加载前开始采集。
- Index.aboutToAppear() 中按序读取:Profile -> StepCount -> Calories -> Location -> Weather。
如果初始化顺序错误(例如先读天气再初始化 Preferences),将导致缓存无法读取,每次启动都会发起网络请求。
导航栈状态管理
NavPathStack 维护页面跳转栈,本应用使用了以下操作:
| 方法 | 用途 | 调用时机 |
|---|---|---|
| pushPath({name}) | 压入新页面 | 跳转到设置页/地图页 |
| clear() | 清空栈 | 设置完成后防止返回 |
| pop() | 弹出栈顶 | 地图页返回 |
设置页面完成后调用 navStack.clear() 而非 navStack.pop(),这是因为 clear() 会移除所有导航记录,确保用户按返回键时不会回到设置页面。而地图页返回时使用默认的导航返回行为(NavDestination 自带的返回按钮),无需手动调用 pop()。
模块间数据流
应用的数据流以 Preferences 为中心枢纽,各模块通过读写 Preferences 实现间接通信:
ProfileSetupPage --saveProfile()--> Preferences --getProfile()--> HomePage/AdvicePage/ProfilePage
DietPage --saveDietRecords()--> Preferences --getTodayCalories()--> HomePage/CalorieSummaryCard
StepService --saveStepCount()--> Preferences --getStepCount()--> HomePage/StepCard
WeatherService --cache--> Preferences --cache--> HomePage/WeatherCard
这种"写入-持久化-读取"模式虽然不是最高效的实时通信方案,但在单 Ability 架构下足够可靠,且天然支持数据持久化(应用重启后数据不丢失)。当 DietPage 添加记录后更新 todayCalories,同时修改 @State todayCalories(由于是同一组件内的 @State,HomePage 的热量卡片会自动刷新)。
Constants 常量组织
Constants.ets 定义了 45+ 常量,按功能分组:
| 分组 | 示例常量 | 用途 |
|---|---|---|
| 存储 | PREFERENCES_NAME, PROFILE_KEY, DIET_RECORD_KEY | Preferences 键名 |
| 跑步类型 | RUN_EASY, RUN_STANDARD, RUN_ENDURANCE | 建议方案标识 |
| 颜色 | COLOR_PRIMARY, COLOR_ORANGE, COLOR_GREEN | UI 主题色 |
| 布局 | MARGIN_CARD, PADDING_PAGE, BORDER_RADIUS_CARD | 间距和圆角 |
| 字体 | FONT_SIZE_TITLE, FONT_SIZE_BODY | 字号 |
| 默认值 | DEFAULT_WEIGHT, DEFAULT_HEIGHT, DEFAULT_DAILY_GOAL | 用户档案默认值 |
| 坐标 | BEIJING_LAT, BEIJING_LNG | 降级定位坐标 |
常量集中管理避免了魔法数字散落在代码各处,修改主题色或布局参数时只需调整一处。
构建配置
应用使用 Hvigor 作为构建系统,关键配置文件:
| 文件 | 用途 |
|---|---|
| build-profile.json5 (根) | 项目级构建配置,签名信息 |
| build-profile.json5 (entry) | 模块级构建配置 |
| hvigorfile.ts | 构建脚本 |
| oh-package.json5 | 依赖管理 |
| module.json5 | 模块配置、权限声明、Ability 注册 |
module.json5 中的 deviceTypes 配置了支持的设备类型(phone、tablet),确保应用仅在适配的设备上安装运行。
更多推荐




所有评论(0)