架构与技术栈

整体架构

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() 中严格遵循依赖关系:

  1. PreferencesService 必须最先初始化(在 EntryAbility.onCreate 中完成),因为其他服务(WeatherService 缓存、StepService 步数存储)依赖它。
  2. LocationEnhanceService.startGyroscope() 在 onCreate 中调用,确保陀螺仪数据在页面加载前开始采集。
  3. 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),确保应用仅在适配的设备上安装运行。

Logo

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

更多推荐