杰理 SDK 文档中心
首页
首页
  • 概述与快速开始

    • 项目简介与功能总览
    • 运行环境与快速开始
    • 工程结构与文档布局
  • 架构与核心机制

    • 插件架构与原生平台桥接
    • 基类管理器与常量体系
    • 事件流与接收通知机制
  • 蓝牙连接与设备管理

    • 蓝牙连接与状态管理
    • 设备信息、配置与按键设置
    • 双设备连接与多链路管理
    • 数据传输与自定义命令
  • 音乐与媒体控制

    • 设备音乐与手机音乐播放控制
    • 音量与音频输出管理
  • 音效与音频模式

    • 均衡器与音效调节
    • 音频模式与降噪(ANC)设置
    • Auracast 音频广播
  • 设备功能控制

    • 闹钟管理
    • FM 收音机控制
    • 灯光控制
    • 充电仓与彩屏仓管理
  • 示例应用:杰理之家 Demo

    • 应用框架与交互组件
    • 设置、多语言与调试
  • 接口参考与文档中心

    • 发送接口参考
    • 接收接口与事件参考
    • 官方文档与集成指南

闹钟管理

闹钟管理是 Flutter 侧通过 BLE 桥接层与设备端 RTC 闹钟进行交互的能力封装,覆盖闹钟列表读取、新增/编辑保存、开关切换、删除、铃声选择、设备时间同步与响铃停止等完整生命周期操作。

Purpose and Scope

本页文档面向 6-device-features.alarm(闹钟管理)目录项,全面说明该能力在 Flutter SDK(jl_home 包)中的实现:

  • 对外 API 层:BleAlarmManager(发送接口)与 BLE 事件流(接收接口)的角色划分;
  • 数据模型:AlarmModel、RingInfoModel、RingModel 的结构与序列化方式;
  • 桥接机制:通过 BleBaseManager.invokeMethod 走方法通道(MethodChannel)到达 Android 原生 SDK 的过程;
  • 关键算法:rtcMode 位掩码表示的循环模式(单次/每天/工作日/自定义星期)解析逻辑;
  • 失败模式、边界情况与扩展点。

以下主题属于其他目录页,不在本页展开:

  • 设备连接与指令分发:BleBaseManager、BleMethodConstants 的底层通道实现,请参见设备通信相关页面;
  • 铃声文件管理:RingInfoModel 中 ringData/fileClus 指向的文件系统操作,请参见文件管理页面;
  • 原生 SDK 内部实现:Android 侧 AlarmManager.kt、AlarmProcessor.kt 等仅作对端说明引用,其协议细节属于原生层文档。

Overview

闹钟管理(Alarm Management)是智能硬件(耳机/音箱类产品)的典型设备功能:设备端维护一组 RTC 闹钟条目,App 负责展示、编辑并下发指令。Flutter-JL_Home 仓库采用 Flutter 逻辑层 + 原生方法通道 + BLE 传输 的三层架构:

  1. Flutter 调用层(libs/Send Interface/ble_alarm_manager.dart):BleAlarmManager 提供全部闹钟操作的静态方法,统一封装参数拼装与结果类型转换,业务代码(UI)只面对强类型的 AlarmModel / RingModel;
  2. 桥接层:每个方法调用 BleBaseManager.invokeMethod,以 BleMethodConstants 中定义的字符串方法名与参数键拼装参数,经方法通道发送到 Android 原生侧;
  3. 原生层(code/JieLi_Home_Demo/android/.../alarm/):AlarmManager.kt、AlarmOperationManager.kt 负责将请求翻译为 BLE 指令包,与设备交互;设备上报的事件由 AlarmProcessor.kt/AlarmHandler.java 处理后经 ble_event_stream.dart 回传给 Flutter 业务层。

该设计的核心意图是将"设备协议细节"与"UI 业务"彻底隔离:Flutter 侧只关心语义化 API(如"获取全部闹钟""保存闹钟"),而协议打包、校验、重发、超时全部收敛在原生层,便于多端复用与协议演进。

Architecture

flowchart TD
    subgraph sg_Flutter["Flutter 应用层"]
        UI["业务 UI / ViewModel"]
        Stream["ble_event_stream.dart<br/>(BLE 事件流)"]
        Processor["ble_alarm_processor.dart<br/>(闹钟事件处理器)"]
    end

    subgraph sg_Send["发送接口层 libs/Send Interface"]
        Manager["BleAlarmManager"]
        Base["BleBaseManager.invokeMethod"]
        Const["BleMethodConstants<br/>(方法名/参数键)"]
    end

    subgraph sg_Model["数据模型层"]
        Alarm["AlarmModel"]
        Ring["RingInfoModel / RingModel"]
    end

    subgraph sg_Native["Android 原生 SDK"]
        AlarmMgr["AlarmManager.kt"]
        AlarmOpMgr["AlarmOperationManager.kt"]
        NativeModel["AlarmModel.kt / AlarmBean.java<br/>AlarmListInfo.java / DefaultAlarmBell.java"]
        AlarmProc["AlarmProcessor.kt / AlarmHandler.java<br/>AlarmNotifyHandle.java"]
    end

    subgraph sg_Device["BLE 设备"]
        Dev["设备 RTC 闹钟存储"]
    end

    UI -->|"静态调用"| Manager
    Manager -->|"构造参数"| Const
    Manager -->|"invokeMethod"| Base
    Base -->|"MethodChannel"| AlarmMgr
    AlarmMgr --> AlarmOpMgr
    AlarmOpMgr --> NativeModel
    AlarmOpMgr -->|"BLE 指令"| Dev
    Dev -->|"事件/通知"| AlarmProc
    AlarmProc --> Processor
    Processor --> Stream
    Stream -->|"事件回调"| UI
    Manager -->|"fromMap/toMap"| Alarm
    Alarm --> Ring

架构要点:

  • 单向调用链(下发):UI → BleAlarmManager → BleBaseManager → 原生 → 设备。BleAlarmManager 的所有方法均为 static,无需实例化,调用方通过 BleMethodConstants 常量保证方法名与参数键不散落为魔法字符串;
  • 反向事件链(上报):设备主动上报的闹钟状态(如响铃中、闹钟更新通知)由原生 AlarmProcessor 解析后经 ble_event_stream 转发给 Flutter 侧 ble_alarm_processor,再分发到业务层,实现"下发-上报"的双向闭环;
  • 模型层解耦:AlarmModel 同时承担"设备协议字段容器"(rtc* 前缀字段)与"UI 展示辅助"(formattedTime、getRepeatDays、ringTypeDescription)两种职责,减少业务侧重复解析代码。

数据模型

AlarmModel

AlarmModel(alarm_model.dart)是闹钟条目的核心模型。字段以 rtc(Real-Time Clock)前缀命名,直接映射设备 RTC 闹钟的协议字段;rtcMode 是位掩码(bitmask),每一位表示星期几重复。

字段类型含义
rtcYear / rtcMonth / rtcDayint闹钟日期(年/月/日),单次闹钟使用
rtcHour / rtcMin / rtcSecint闹钟时间(时/分/秒)
rtcEnablebool闹钟开关状态
rtcModeint循环模式位掩码(0=单次,bit0=每天,bit1~bit7=周一~周日)
rtcIndexint闹钟在设备列表中的索引(0 起)
rtcNameString闹钟名称
ringInfoRingInfoModel?铃声信息(类型 + 具体铃声数据),可空
ringDataString?铃声原始数据(如自定义铃声标识),可空

模型提供三条序列化/克隆路径:

  • factory AlarmModel.fromMap(dynamic mapData):从原生层返回的 Map 构建模型。实现上先做一次类型归一化——把非 String 键统一 toString() 转成字符串键,再对 ringInfo 做空值与类型双重安全检查后才调用 RingInfoModel.fromMap,其余字段全部用 ?? 提供默认值,避免原生层缺字段时抛空指针:

    factory AlarmModel.fromMap(dynamic mapData) {
      final Map<String, dynamic> map = {};
      if (mapData is Map) {
        mapData.forEach((key, value) {
          if (key is String) {
            map[key] = value;
          } else if (key != null) {
            map[key.toString()] = value;
          }
        });
      }
      // 安全处理 ringInfo
      RingInfoModel? ringInfo;
      if (map['ringInfo'] != null && map['ringInfo'] is Map) {
        ringInfo = RingInfoModel.fromMap(map['ringInfo']);
      }
      return AlarmModel(
        rtcYear: map['rtcYear'] ?? 0,
        // ... 其余字段均带 ?? 默认值
        ringInfo: ringInfo,
        ringData: map['ringData'],
      );
    }
    

    Source: alarm_model.dart

  • toMap():反向序列化,供 saveAlarmInfo / selectRing 等写操作拼装参数;ringInfo 为空时序列化为 null;

  • copyWith(...):不可变风格的部分更新,业务层修改单个字段(如切换 rtcEnable)时使用。

RingInfoModel / RingModel

  • RingInfoModel(定义于 ring_info_model.dart,由 AlarmModel 导入)描述闹钟绑定的铃声:type 字段区分铃声来源——0x00 设备默认铃声、0x01 媒体(文件)选择铃声;AlarmModel.ringTypeDescription 基于该字段给出可读描述;
  • RingModel(libs/model/ring_model.dart)是铃声列表项模型,至少包含 index 与 name 字段——getDefaultRings() 的兜底分支 RingModel(index: 0, name: 'Unknown ring') 验证了这一点,用于设备默认铃声列表的展示。

循环模式位掩码解析

rtcMode 是闹钟重复策略的紧凑编码,一个 int 承载 8 个布尔位。getRepeatDays(Map<String, String> localizedDays)(alarm_model.dart)按如下优先级把位掩码翻译为本地化星期文案:

flowchart TD
    Start([getRepeatDays]) --> Mode0{"rtcMode == 0?"}
    Mode0 -->|"是"| Once["返回 ['once'] 单次"]
    Mode0 -->|"否"| Bit0{"bit0 == 1?"}
    Bit0 -->|"是"| Every["返回 ['everyDay'] 每天"]
    Bit0 -->|"否"| Week6{"bit1-bit6 全为 1?"}
    Week6 -->|"是"| MonSat["周一至周六"]
    Week6 -->|"否"| Week5{"bit1-bit5 全为 1?"}
    Week5 -->|"是"| Workdays["工作日(周一至周五)"]
    Week5 -->|"否"| Each["按 bit1-bit7 逐位收集<br/>周一 ~ 周日"]

解析顺序的设计意图:

  1. 特例优先:rtcMode == 0 表示"单次"、bit0 表示"每天",这两个是最高频且语义最简的配置,先短路返回,避免落入逐位收集的通用分支;
  2. 组合特例:bit1~bit6 全为 1 时折叠为"周一至周六",bit1~bit5 全为 1 时折叠为"工作日(周一至周五)"——这两类在消费电子产品中极为常见(上学/通勤场景),折叠后可让 UI 直接展示短语而非六个星期标签;
  3. 通用回退:其余情况逐位检查 bit1(周一)到 bit7(周日),按位序追加本地化名称;
  4. 可本地化:方法接收 localizedDays 字典而非硬编码文案,键名 once/everyDay/monday...sunday,UI 层可注入任意语言。
// 特例:每天 (bit0 = 1)
if (bt_0 == 1) {
  return [localizedDays['everyDay'] ?? 'Every day'];
}
// 工作日 (周一至周五,bit1-bit5 全部为1)
if (bt_5 + bt_4 + bt_3 + bt_2 + bt_1 == 5) {
  return [
    localizedDays['monday'] ?? 'Mon',
    // ... tuesday ~ friday
  ];
}
// 单独处理每一天
if (bt_1 == 1) days.add(localizedDays['monday'] ?? 'Mon');
if (bt_2 == 1) days.add(localizedDays['tuesday'] ?? 'Tue');
// ... bt_3 ~ bt_7
return days;

Source: alarm_model.dart

模型层还提供 formattedTime(HH:mm 补零)与 formattedDate(yyyy-MM-dd)两个计算属性,供列表/编辑页直接渲染,避免业务层重复格式化。

核心流程

下发流程:获取闹钟列表

以 getAllAlarmList() 为例,展示完整的数据往返路径(ble_alarm_manager.dart):

sequenceDiagram
    participant UI as 业务 UI
    participant Mgr as BleAlarmManager
    participant Base as BleBaseManager
    participant Native as 原生 AlarmManager.kt
    participant Dev as BLE 设备

    UI->>Mgr: getAllAlarmList()
    activate Mgr
    Mgr->>Base: invokeMethod(methodGetAllAlarmList)
    activate Base
    Base->>Native: MethodChannel 调用
    activate Native
    Native->>Dev: 发送 BLE 查询指令
    Dev-->>Native: 返回闹钟列表数据
    Native-->>Base: List<dynamic> (Map 列表)
    deactivate Native
    Base-->>Mgr: List<dynamic>
    deactivate Base
    Mgr->>Mgr: _convertToAlarmModels 遍历并 fromMap
    Mgr-->>UI: List<AlarmModel>
    deactivate Mgr

写操作流程:保存闹钟

写操作(新增/编辑/开关/删除/选铃)统一遵循 invokeMethod(方法名, arguments: {参数键: 值}) 模式。以保存为例,saveAlarmInfo(alarmMap, isNewAlarm) 携带完整闹钟 Map 与新增标志两个参数下发给原生层;原生层根据 isNewAlarm 决定走"插入新槽位"还是"覆盖 rtcIndex 指向的槽位"协议分支。选择铃声的流程类似,但参数不同:selectRing 用 ringIndex 指向设备默认铃声表,selectCardRing 用 cardType + fileClus 指向存储卡上的铃声文件,二者是设备端两类铃声来源(内置 vs 文件)的不同寻址方式。

Usage Examples

示例一:读取并展示闹钟列表

getAllAlarmList() 返回原生层原始 List<dynamic>,_convertToAlarmModels 只接受 Map 元素并逐个 AlarmModel.fromMap 转换,非 Map 元素被静默跳过——这一宽容策略保证原生层混入异常元素时列表不整体崩溃:

static Future<List<AlarmModel>> getAllAlarmList() async {
  try {
    final List<dynamic> result = await BleBaseManager.invokeMethod(
      BleMethodConstants.methodGetAllAlarmList,
    );
    return _convertToAlarmModels(result);
  } catch (e) {
    rethrow;
  }
}

static List<AlarmModel> _convertToAlarmModels(List<dynamic> rawData) {
  List<AlarmModel> alarmList = [];
  for (var item in rawData) {
    if (item is Map) {
      final alarm = AlarmModel.fromMap(item);
      alarmList.add(alarm);
    }
  }
  return alarmList;
}

Source: ble_alarm_manager.dart

示例二:新增/编辑闹钟

业务层把 AlarmModel 转为 Map 后调用 saveAlarmInfo;isNewAlarm 决定设备端是插入还是覆盖。注意该方法返回 bool,调用方可以据此向用户反馈保存是否成功:

/// Save alarm information
static Future<bool> saveAlarmInfo(
  Map<String, dynamic> alarmMap,
  bool isNewAlarm,
) async {
  return await BleBaseManager.invokeMethod(
    BleMethodConstants.methodSaveAlarm,
    arguments: {
      BleMethodConstants.argAlarm: alarmMap,
      BleMethodConstants.argIsNewAlarm: isNewAlarm,
    },
  );
}

Source: ble_alarm_manager.dart

示例三:选择铃声(内置铃声 vs 存储卡铃声)

两条选铃路径共享 alarm(目标闹钟)与 ringName 参数,区别在铃声寻址方式:selectRing 用 ringIndex(设备默认铃声表下标),selectCardRing 用 cardType + fileClus(文件系统簇号)。两者均在异常时返回 false 而非抛出,适合 UI 直接据此提示失败:

/// Set selected ringtone
static Future<bool> selectRing({
  required Map<String, dynamic> alarm,
  required String ringName,
  required int ringIndex,
}) async {
  try {
    final result = await BleBaseManager.invokeMethod(
      BleMethodConstants.methodSelectAlarmRing,
      arguments: {
        BleMethodConstants.argAlarm: alarm,
        BleMethodConstants.argRingName: ringName,
        BleMethodConstants.argIndex: ringIndex,
      },
    );
    return result == true;
  } catch (e) {
    return false;
  }
}

/// Set card selected ringtone
static Future<bool> selectCardRing({
  required Map<String, dynamic> alarm,
  required String ringName,
  required int cardType,
  required int fileClus,
}) async {
  try {
    final result = await BleBaseManager.invokeMethod(
      BleMethodConstants.methodSelectAlarmCardRing,
      arguments: {
        BleMethodConstants.argAlarm: alarm,
        BleMethodConstants.argRingName: ringName,
        BleMethodConstants.argCardType: cardType,
        BleMethodConstants.argFileClus: fileClus,
      },
    );
    return result == true;
  } catch (e) {
    return false;
  }
}

Source: ble_alarm_manager.dart

方法常量与参数约定

所有方法名与参数键集中在 BleMethodConstants(libs/constant/ble_method_constants.dart),由 BleAlarmManager 引用,保证 Flutter 与原生侧契约单一来源。从 ble_alarm_manager.dart 的实际调用可归纳出闹钟域使用的常量:

常量(方法)用途携带参数
methodGetAllAlarmList获取全部闹钟列表无
methodSaveAlarm保存(新增/编辑)闹钟argAlarm(完整闹钟 Map)、argIsNewAlarm(bool)
methodUpdateAlarmState更新闹钟开关argIndex、argAlarmStatus
methodDeleteCurrentAlarm删除指定闹钟argIndex
methodSetDeviceSyncTime同步设备时间无
methodGetDefaultRings获取设备默认铃声列表无
methodSelectAlarmRing选择内置默认铃声argAlarm、argRingName、argIndex
methodSelectAlarmCardRing选择存储卡铃声argAlarm、argRingName、argCardType、argFileClus
methodStopAlarmBell停止响铃无

表中方法名/参数键均取自 ble_alarm_manager.dart 的实际引用;常量文件本身未在本页逐一展开,其定义应与上表一致。

API Reference

BleAlarmManager

全部为 static 方法,调用前需确保 BLE 已连接(连接管理见设备通信相关页面)。

static Future<List<AlarmModel>> getAllAlarmList()

获取设备全部闹钟。

  • 返回:List<AlarmModel>;转换过程中非 Map 元素被跳过。
  • 抛出:rethrow 原样向上传播底层(通道/原生)异常,调用方需自行 try-catch。

static Future<void> updateAlarmStatus(int index, bool status)

切换指定索引闹钟的开关状态。

  • 参数:index(int,闹钟槽位索引);status(bool,目标开关状态)。
  • 返回:void;无失败反馈,调用方需结合事件流或重新拉取列表确认。

static Future<void> deleteAlarm(int index)

删除指定索引闹钟。

  • 参数:index(int,闹钟槽位索引)。
  • 返回:void。

static Future<bool> setDeviceSyncTime()

以手机当前时间同步设备 RTC。

  • 返回:bool,同步是否成功。通常在闹钟列表页进入前调用,保证设备端时间基准正确。

static Future<bool> saveAlarmInfo(Map<String, dynamic> alarmMap, bool isNewAlarm)

保存闹钟信息。

  • 参数:alarmMap(AlarmModel.toMap() 产物);isNewAlarm(true=新增,false=覆盖编辑)。
  • 返回:bool 保存结果。

static Future<List<RingModel>> getDefaultRings()

获取设备默认铃声列表。

  • 返回:List<RingModel>;通道异常时返回空列表(内部吞掉异常),非 Map 元素兜底为 RingModel(index: 0, name: 'Unknown ring')。

static Future<bool> selectRing({required Map<String, dynamic> alarm, required String ringName, required int ringIndex})

为闹钟选择设备内置默认铃声。

  • 参数:alarm(目标闹钟 Map);ringName(铃声名);ringIndex(铃声在默认铃声表中的下标)。
  • 返回:bool;异常时返回 false。

static Future<bool> selectCardRing({required Map<String, dynamic> alarm, required String ringName, required int cardType, required int fileClus})

为闹钟选择存储卡(TF 卡)上的铃声文件。

  • 参数:cardType(存储卡类型)、fileClus(文件起始簇号),二者共同定位文件系统中的铃声。
  • 返回:bool;异常时返回 false。

static Future<void> stopAlarmBell()

停止设备当前响铃(如用户点击"关闭闹铃")。

  • 返回:void。

Failure Modes、边界情况与并发

失败模式

  • 通道层异常:getAllAlarmList 采用 rethrow,任何通道/原生异常都会冒泡——这是有意为之:列表是后续操作的前提,失败必须可见;而 selectRing / selectCardRing / getDefaultRings 采用内部捕获并返回兜底值(false / 空列表),因为选铃失败不应中断整个页面流程,UI 只需提示失败即可;
  • 原生缺字段:AlarmModel.fromMap 对所有字段提供 ?? 默认值(数值 0、布尔 false、字符串空串),旧固件返回的残缺数据不会导致解析崩溃,但可能呈现"1970-01-01 00:00"之类的空闹钟——业务层应过滤 rtcIndex 无效或全零的条目;
  • 异常元素注入:_convertToAlarmModels 对非 Map 元素静默跳过,避免单条脏数据拖垮整个列表。

边界情况

  • 索引寻址:updateAlarmStatus / deleteAlarm 以 rtcIndex 为唯一寻址依据。设备端槽位在删除后是否重排由原生协议决定,Flutter 侧每次操作后应重新拉取列表,否则本地缓存的索引可能错位;
  • 单次闹钟:rtcMode == 0 表示单次闹钟,依赖 rtcYear/Month/Day 日期字段;编辑此类闹钟切换为重复模式时,业务层需同步清理/保留日期字段,模型层本身不做该判断;
  • 空铃声:ringInfo 可空,ringTypeDescription 对空值返回 'Unknown',UI 层需处理"未选铃声"状态。

并发与一致性

  • BleAlarmManager 为无状态静态类,本身无并发问题;但底层方法通道与 BLE 链路通常串行化指令,连续快速调用写操作(如连点开关)可能被原生层排队或丢弃,业务层应做防抖或依赖事件流确认最终状态;
  • 开关切换后设备若在本地播放/停止响铃,状态变化经由事件流异步回传,UI 侧以事件流为准而非本地乐观更新,避免状态回跳。

性能与运维注意事项

  • getAllAlarmList 每次全量拉取,闹钟数量通常很小(个位数~十位数),无需分页;但 BLE 传输速率低,涉及铃声数据(ringData)时注意控制单次载荷;
  • setDeviceSyncTime 建议在闹钟编辑页首次展示前调用一次即可,避免频繁同步消耗电量;
  • 所有方法最终都走 MethodChannel,Android 主线程与 BLE 工作线程的切换由原生层处理;Flutter 侧 Future 天然异步,不会阻塞 UI。

扩展点

  1. 新闹钟操作:在 BleAlarmManager 增加静态方法,遵循 invokeMethod(常量方法名, arguments) 模式,并在 BleMethodConstants 补充方法名/参数键常量,同时扩展原生侧 AlarmOperationManager 的对应协议实现;
  2. 铃声来源扩展:RingInfoModel.type 目前区分 0x00 内置与 0x01 媒体文件;若新增来源(如云铃声),可在 ringTypeDescription 增加分支并新增对应选铃方法;
  3. 循环模式新语义:getRepeatDays 的位掩码约定若被新固件扩展(如节假日模式),需在该方法中补充特例分支;注意保持现有特例优先级,避免破坏旧固件兼容;
  4. 事件流消费:ble_event_stream.dart 中注册 ble_alarm_processor 后,业务层可订阅闹钟相关事件(如响铃中/停止),实现"设备端响铃时 App 弹窗"等交互。

Tests

本页覆盖的代码(BleAlarmManager、AlarmModel)未在本次检索中发现独立测试文件。从实现可推导的推荐测试点:

  • AlarmModel.fromMap 的缺字段兜底与 ringInfo 空值/非 Map 分支;
  • getRepeatDays 的位掩码矩阵:0(单次)、bit0(每天)、bit1~6 全置位(周一至周六)、bit1~5 全置位(工作日)、任意组合(自定义星期);
  • _convertToAlarmModels 混入非 Map 元素的过滤行为;
  • selectRing/selectCardRing 异常路径返回 false。

Related Links

  • BleAlarmManager 实现(发送接口)
  • AlarmModel 数据模型
  • BLE 事件流(接收接口)
  • Android 原生 AlarmManager.kt
  • Android 原生 AlarmOperationManager.kt
  • Android 原生 AlarmProcessor.kt
  • 设备通信与方法通道:见设备通信相关目录页(BleBaseManager / BleMethodConstants)
  • 铃声文件管理:见文件管理相关目录页(RingInfoModel / ringData)
Next
FM 收音机控制