# 模块拆分 - 应用执行层 (Docs/50_module-breakdown/mod-app.md) 本模块描述手环的 App 调度管理器(AppManager)、前台/后台应用架构、生命周期钩子(OnStart, onRun, onClose, onEvent)、事件分发机制、CPU占用控制以及各功能模块的页面渲染与状态机调度设计。 --- ## 1. 模块职责说明 ### 1.1 核心职责 | 职责领域 | 描述 | | :--- | :--- | | **应用生命周期管理** | 维护应用注册表,管理应用的启动、切换、关闭流程 | | **前台/后台应用分离** | 支持前台应用独占屏幕和后台应用持续监听的双轨运行模式 | | **事件分发路由** | 实现前台优先、后台兜底、未处理丢弃的事件分发策略 | | **CPU占用控制** | 监控应用 onRun() 执行时间,防止单个应用长时间占用CPU | | **应用状态维护** | 维护活跃应用指针、应用运行状态标记等核心数据结构 | ### 1.2 应用类型划分 系统将应用分为两大类,通过 `AppType` 枚举区分: | 应用类型 | 标识 | 特性 | 生命周期 | | :--- | :--- | :--- | :--- | | **前台应用** | `APP_TYPE_FOREGROUND` | 独占屏幕显示,响应用户交互,一次只有一个活跃 | `OnStart` → `onRun`(循环) → `onClose` | | **后台应用** | `APP_TYPE_BACKGROUND` | 无屏幕显示,持续运行监听特定事件,可多个同时运行 | 注册后自动运行,无显式启动/关闭 | ### 1.3 应用列表 | 应用ID | 应用名称 | 类型 | 职责描述 | | :--- | :--- | :--- | :--- | | `APP_ID_CLOCK` | ClockApp | 前台 | 待机时钟应用,中央以 32x64px 超大点阵渲染当前时分,右上角渲染 16x32px 电量百分比及充电指示;后台监听射频报警信号 | | `APP_ID_MENU` | MenuApp | 前台 | 主菜单应用,单屏单条目显示交互,支持 ▲/▼ 键高亮切换,确认键进入对应子页面 | | `APP_ID_PAIR` | PairApp | 前台 | 配对与微调应用,对码捕捉到未配对 24 位射频码时拉起,提供微调序号选框,确认后存入持久化 Flash | | `APP_ID_ALARM` | AlarmApp | 前台 | 传感器报警应用,被后台监测唤醒拉起,屏幕闪烁红色粗边框,调度马达振动波形与 LED 警示颜色 | | `APP_ID_SOS` | SOSApp | 前台 | 主动求救应用,短按/长按 SOS 物理按键拉起,周期性发射 0x08 (SOS) 无线电数据,触发最高优先级的持续马达震动及红色幻彩灯爆闪 | --- ## 2. 核心数据结构 ### 2.1 应用描述符结构 (WristbandApp) ```c typedef struct { AppID app_id; // 应用唯一标识 (APP_ID_CLOCK/MENU/PAIR/ALARM/SOS) AppType app_type; // 应用类型 (APP_TYPE_FOREGROUND / APP_TYPE_BACKGROUND) char *app_name; // 应用名称(用于调试日志输出) // 生命周期回调函数指针 void (*OnStart)(void); // 启动回调:应用切入前台,加载资源、初始化显示 void (*onRun)(void); // 循环回调:主循环轮询调用,执行非阻塞业务逻辑 void (*onClose)(void); // 关闭回调:应用退出前台,保存状态、清理外设 EventResult (*onEvent)(SystemEvent evt); // 事件回调:接收并处理系统事件 // 运行状态标记 bit is_active; // 当前是否活跃(仅对前台应用有效,标识是否在前台运行) bit is_running; // 应用是否处于运行状态(用于CPU占用控制,标记是否允许调用onRun) } WristbandApp; ``` ### 2.2 应用ID枚举 ```c typedef enum { APP_ID_CLOCK, // 0: 待机时钟应用 APP_ID_MENU, // 1: 主菜单应用 APP_ID_PAIR, // 2: 配对与微调应用 APP_ID_ALARM, // 3: 传感器报警应用 APP_ID_SOS, // 4: 主动求救应用 APP_ID_MAX // 5: 应用数量上限(用于数组大小) } AppID; ``` ### 2.3 应用类型枚举 ```c typedef enum { APP_TYPE_FOREGROUND, // 前台应用:独占屏幕和用户交互 APP_TYPE_BACKGROUND // 后台应用:无屏幕显示,仅监听事件 } AppType; ``` ### 2.4 事件处理返回值 ```c typedef enum { EVENT_HANDLED, // 事件已处理,停止继续分发 EVENT_IGNORED // 事件未处理,继续传递给下一个应用 } EventResult; ``` --- ## 3. 应用管理器核心接口 ### 3.1 初始化与注册接口 | 接口名称 | 函数签名 | 功能描述 | | :--- | :--- | :--- | | `AppManager_Init` | `void AppManager_Init(void)` | 初始化应用管理器,初始化应用注册表,设置默认活跃应用为 ClockApp | | `AppManager_RegisterApp` | `void AppManager_RegisterApp(AppID app_id, AppType app_type, char *app_name, AppStartFunc on_start, AppRunFunc on_run, AppCloseFunc on_close, AppEventFunc on_event)` | 注册应用到注册表,绑定生命周期回调函数 | ### 3.2 应用切换接口 | 接口名称 | 函数签名 | 功能描述 | | :--- | :--- | :--- | | `AppManager_StartApp` | `void AppManager_StartApp(AppID app_id)` | 启动指定应用:关闭当前活跃应用 → 切换活跃应用指针 → 启动新应用 | | `AppManager_CloseApp` | `void AppManager_CloseApp(void)` | 关闭当前活跃应用,回退到默认应用(ClockApp) | | `AppManager_GetActiveAppID` | `AppID AppManager_GetActiveAppID(void)` | 获取当前活跃应用的 ID | ### 3.3 事件分发接口 | 接口名称 | 函数签名 | 功能描述 | | :--- | :--- | :--- | | `AppManager_DispatchEvent` | `void AppManager_DispatchEvent(SystemEvent evt)` | 分发事件到应用层:前台优先 → 后台兜底 → 未处理丢弃 | | `AppManager_RunActiveApp` | `void AppManager_RunActiveApp(void)` | 运行当前活跃应用的 onRun 回调(带 CPU 占用控制) | --- ## 4. 应用生命周期调度机制 ### 4.1 生命周期状态转换 ```mermaid graph LR subgraph 前台应用状态 Idle["未注册 / 空闲"] -->|注册| Registered["已注册"] Registered -->|StartApp| Active["活跃中 (OnStart)"] Active -->|onRun循环| Running["运行中"] Running -->|StartApp(其他)| Closing["关闭中 (onClose)"] Closing -->|切换完成| Active Running -->|CloseApp| Closing Closing -->|回退完成| Default["默认状态 (ClockApp)"] end subgraph 后台应用状态 BG_Idle["未注册"] -->|注册| BG_Running["持续运行"] BG_Running -->|接收事件| BG_Handling["事件处理中"] BG_Handling -->|处理完成| BG_Running end ``` ### 4.2 生命周期回调语义 | 回调函数 | 调用时机 | 职责要求 | | :--- | :--- | :--- | | `OnStart()` | 应用切入前台时 | 初始化显示、加载资源、重置状态变量 | | `onRun()` | 主循环每轮调用 | 执行非阻塞业务逻辑(如动画更新、超时检测),必须控制执行时间 | | `onClose()` | 应用退出前台时 | 保存状态、清理外设(关屏、停振、关闭射频等) | | `onEvent(evt)` | 收到事件时 | 处理事件,返回 `EVENT_HANDLED` 或 `EVENT_IGNORED` | ### 4.3 应用切换流程 ``` AppManager_StartApp(target_app_id) 执行流程: 1. 参数有效性检查 ├── app_id >= APP_ID_MAX → 返回错误 ├── 目标应用非前台应用 → 返回错误 └── 目标应用未注册 (OnStart == NULL) → 返回错误 2. 目标应用已是活跃应用 → 直接返回 3. 关闭当前活跃应用 └── active_app->onClose() → 清理外设、保存状态 4. 切换活跃应用指针 └── active_app = &app_registry[target_app_id] 5. 启动新应用 └── active_app->OnStart() → 初始化显示、加载资源 ``` --- ## 5. 事件分发机制 ### 5.1 事件分发规则 事件在应用层的分发遵循以下顺序,前台应用优先处理,后台应用作为兜底: ``` 事件分发流程 (AppManager_DispatchEvent): ┌──────────────────────────────────────────────────────────────┐ │ Step 1: 前台应用优先处理 │ │ ┌────────────────────────────────────────────────────────┐ │ │ │ if (active_app != NULL && active_app->is_active) │ │ │ │ { │ │ │ │ result = active_app->onEvent(evt); │ │ │ │ if (result == EVENT_HANDLED) │ │ │ │ return; // 前台应用已处理,结束分发 │ │ │ │ } │ │ │ └────────────────────────────────────────────────────────┘ │ ├──────────────────────────────────────────────────────────────┤ │ Step 2: 后台应用兜底处理 │ │ ┌────────────────────────────────────────────────────────┐ │ │ │ for (i = 0; i < APP_ID_MAX; i++) │ │ │ │ { │ │ │ │ bg_app = &app_registry[i]; │ │ │ │ // 仅处理后台应用 │ │ │ │ if (bg_app->app_type != APP_TYPE_BACKGROUND || │ │ │ │ bg_app->onEvent == NULL) │ │ │ │ continue; │ │ │ │ │ │ │ │ result = bg_app->onEvent(evt); │ │ │ │ if (result == EVENT_HANDLED) │ │ │ │ return; // 某后台应用已处理,结束分发 │ │ │ │ } │ │ │ └────────────────────────────────────────────────────────┘ │ ├──────────────────────────────────────────────────────────────┤ │ Step 3: 事件丢弃 │ │ ┌────────────────────────────────────────────────────────┐ │ │ │ // 前台和后台应用均未处理,事件被丢弃 │ │ │ └────────────────────────────────────────────────────────┘ │ └──────────────────────────────────────────────────────────────┘ ``` ### 5.2 事件处理示例 以下为各应用的典型事件处理行为: | 事件类型 | ClockApp | MenuApp | PairApp | AlarmApp | SOSApp | | :--- | :--- | :--- | :--- | :--- | :--- | | `KEY_EVENT_UP_CLICK` | IGNORED | HANDLED (上移) | IGNORED | HANDLED (清除) | IGNORED | | `KEY_EVENT_DOWN_CLICK` | IGNORED | HANDLED (下移) | IGNORED | HANDLED (清除) | IGNORED | | `KEY_EVENT_SOS_CLICK` | HANDLED (启动SOS) | IGNORED | IGNORED | HANDLED (清除) | IGNORED | | `KEY_EVENT_UP_DOWN_COMB` | HANDLED (启动Menu) | IGNORED | IGNORED | IGNORED | IGNORED | | `KEY_EVENT_SOS_LONG` | HANDLED (绑定IPC) | IGNORED | HANDLED (保存) | IGNORED | HANDLED (退出) | | `KEY_EVENT_DOWN_LONG` | IGNORED | HANDLED (返回) | HANDLED (退出) | IGNORED | HANDLED (退出) | --- ## 6. CPU 占用控制机制 ### 6.1 设计目的 为防止单个应用的 `onRun()` 回调长时间占用 CPU 导致系统响应迟钝或卡死,框架引入 **执行时间监控机制**。 ### 6.2 控制参数 | 参数名称 | 定义 | 默认值 | 说明 | | :--- | :--- | :--- | :--- | | `APP_ONRUN_MAX_TICKS` | onRun 最大执行时间阈值 | 5 (ms) | 超过此时间则暂停应用运行 | ### 6.3 监控机制 ``` AppManager_RunActiveApp() 执行流程: 1. 检查应用状态 └── if (active_app == NULL || !active_app->is_active) → 直接返回 2. 记录开始时间 └── start_tick = ms_tick 3. 执行应用的 onRun() 回调 └── active_app->onRun() 4. 计算执行耗时 └── elapsed_ticks = ms_tick - start_tick 5. 判断是否超时 ├── if (elapsed_ticks > APP_ONRUN_MAX_TICKS) │ └── active_app->is_running = 0 // 暂停应用 └── else └── active_app->is_running = 1 // 继续运行 ``` ### 6.4 超时恢复 * 超时后应用的 `is_running` 标记被置为 0,下一次主循环将跳过该应用的 `onRun()` 调用 * 应用可在下一次循环中自动恢复运行(`is_running` 在 `onRun` 调用前被检查,调用后被更新) * 此机制不会杀死应用,仅暂停其循环执行,允许应用在后续恢复 --- ## 7. 应用实现细节 ### 7.1 ClockApp(时钟应用) **职责**:待机时钟显示、后台射频监测 **生命周期实现**: | 回调 | 实现逻辑 | | :--- | :--- | | `OnStart` | 初始化状态为 NORMAL,设置重绘标志 | | `onRun` | 分钟变化时重绘时钟;后台监听射频信号,匹配到已配对传感器触发报警 | | `onClose` | 清理资源 | | `onEvent` | 处理组合键进入菜单、SOS 按键进入求救模式、长按绑定 IPC | ### 7.2 MenuApp(菜单应用) **职责**:主菜单导航 **生命周期实现**: | 回调 | 实现逻辑 | | :--- | :--- | | `OnStart` | 初始化菜单选择项为 0,关闭射频,显示菜单页面 | | `onRun` | 无持续运行逻辑 | | `onClose` | 重新开启射频接收 | | `onEvent` | 处理上/下键切换菜单项、确认键进入配对、长按返回时钟 | ### 7.3 PairApp(配对应用) **职责**:传感器配对与保存 **生命周期实现**: | 回调 | 实现逻辑 | | :--- | :--- | | `OnStart` | 进入等待状态,开启射频接收,显示雷达搜索页面 | | `onRun` | 更新雷达动画;监听射频信号捕获对码;检测超时 | | `onClose` | 关闭射频,重置配对状态 | | `onEvent` | 处理长按退出、确认保存对码 | ### 7.4 AlarmApp(报警应用) **职责**:入侵警报显示与响应 **生命周期实现**: | 回调 | 实现逻辑 | | :--- | :--- | | `OnStart` | 设置状态为 ALARM,显示报警页面,重置报警定时器 | | `onRun` | 控制报警边框闪烁效果 | | `onClose` | 停止报警效果(马达、LED),恢复正常状态 | | `onEvent` | 任何按键清除报警 | ### 7.5 SOSApp(求救应用) **职责**:主动求救模式 **生命周期实现**: | 回调 | 实现逻辑 | | :--- | :--- | | `OnStart` | 设置状态为 SOS_EMITTED,显示 SOS 页面 | | `onRun` | 每 500ms 发射一次射频求救帧;控制红色边框闪烁 | | `onClose` | 停止求救效果(马达、LED),恢复正常状态 | | `onEvent` | 长按任意键解除求救 | --- ## 8. 页面渲染接口 | 接口名称 | 函数签名 | 所属应用 | 功能描述 | | :--- | :--- | :--- | :--- | | `UI_ShowClockPage` | `void UI_ShowClockPage(SystemState state, u8 hour, u8 min)` | ClockApp | 绘制时钟页面,包含时间显示和电量指示 | | `UI_ShowPairMenuPage` | `void UI_ShowPairMenuPage(u8 selected_item)` | MenuApp | 绘制配对菜单页面,显示选中项图标和名称 | | `UI_ShowPairWaitPage` | `void UI_ShowPairWaitPage(u8 frame)` | PairApp | 绘制雷达搜索页面,包含动画帧 | | `UI_ShowPairConfirmPage` | `void UI_ShowPairConfirmPage(u32 addr)` | PairApp | 绘制配对确认页面,显示捕获到的射频地址 | | `UI_ShowPairSuccessPage` | `void UI_ShowPairSuccessPage(u32 addr)` | PairApp | 绘制配对成功页面 | | `UI_ShowPairFailPage` | `void UI_ShowPairFailPage(u8 reason)` | PairApp | 绘制配对失败页面 | | `UI_ShowAlarmPage` | `void UI_ShowAlarmPage(char *name, u8 severity)` | AlarmApp | 绘制报警页面,包含红色边框和传感器名称 | | `UI_ShowSosPage` | `void UI_ShowSosPage(void)` | SOSApp | 绘制 SOS 求救页面 | | `UI_ShowBindingPage` | `void UI_ShowBindingPage(void)` | ClockApp | 绘制 IPC 绑定页面 | --- ## 9. 应用注册表结构 应用管理器维护一个静态数组 `app_registry[APP_ID_MAX]`,存储所有已注册的应用: ```c static WristbandApp app_registry[APP_ID_MAX]; static WristbandApp *active_app = NULL; // 当前活跃前台应用指针 #define DEFAULT_APP_ID APP_ID_CLOCK // 默认应用为时钟 ``` 注册流程: 1. 系统初始化时调用 `AppManager_Init()` 初始化注册表 2. 各应用模块调用 `AppManager_RegisterApp()` 注册自身 3. 最后调用 `AppManager_Init()` 设置默认活跃应用 --- ## 10. 主循环集成 主循环中应用管理器的调用顺序: ``` while(1) { // 1. 处理串口调试输入 cmd = Uart_RxChar(); if (cmd != '\0') // 解析命令,生成事件,调用 EventQueue_Push 或 EventQueue_InsertFront // 2. 处理按键事件缓冲区 if (key_event_buf != KEY_EVENT_NONE) // 生成事件,根据优先级选择入队(Push)或插队(InsertFront) key_event_buf = KEY_EVENT_NONE; // 3. 处理事件队列 while (!EventQueue_IsEmpty()) { evt = EventQueue_Pop(); AppManager_DispatchEvent(evt); } // 4. 运行当前活跃应用(带CPU占用控制) AppManager_RunActiveApp(); // 5. 其他系统监控(如LED/马达状态输出) } ```