设备信息、配置与按键设置
本页介绍 JL_Home SDK 中设备信息获取、配置数据转换与按键(keySettings)/LED(ledSettings)设置的完整数据链路:从 Flutter 管理器的 MethodChannel 调用,到数据模型转换,再到示例 App 的设置页面渲染与刷新。
Purpose and Scope
本页覆盖以下能力:
- 设备信息查询:
BleDeviceInfoManager提供设备类型、设备信息、底部卡片、功能列表等查询接口; - 配置数据转换:示例 App 的
DeviceInfoManager将原生返回的Map<Object?, Object?>转换为可渲染的配置结构,重点处理keySettings(按键设置)与ledSettings(LED 设置)列表; - 按键设置与配置页面:
device_settings_page.dart如何加载、展示并在配置变更后刷新设备信息; - 方法常量桥接:
BleMethodConstants中与本能力相关的方法名常量定义。
以下内容不在本页范围,属于姊妹页面:BLE 连接与断连流程(见"连接管理"页)、OTA 升级、音乐/闹钟管理、灯光(light)设置等独立能力。
Overview
JL_Home 是一个基于 Flutter 的杰理(Jieli)蓝牙音频设备 App 示例。SDK 采用分层桥接架构:Dart 层管理器(lib/manager/)通过 BleBaseManager.invokeMethod 调用原生平台(Android/iOS)的 MethodChannel 方法,原生 SDK 再与 BLE 设备通信,把设备能力以 JSON/Map 形式回传。
设备信息是本能力的数据基础。一次 getDeviceInfo 调用返回的 Map 中通常包含:
- 设备类型、名称、协议版本等基础属性;
keySettings:按键事件 → 动作映射表(例如单击/双击/长按对应的功能);ledSettings:LED 灯效配置表;- 设备支持的功能列表与底部卡片类型等。
设计意图:信息获取与 UI 渲染解耦。SDK 侧只提供原始数据接口,示例 App 侧用 DeviceInfoManager 做"翻译层",这样不同设备固件的字段差异被隔离在转换层内,页面代码只需消费统一的 Map<String, dynamic>。
Architecture
flowchart TD
subgraph sg_App["示例 App(JieLi_Home_Demo)"]
Page["DeviceSettingsPage<br/>device_settings_page.dart"]
DM["DeviceInfoManager<br/>数据转换层"]
BDM["BleDeviceInfoManager<br/>设备信息管理器"]
BSM["BleDeviceSettingManager<br/>设置管理器"]
end
subgraph sg_Sdk["jl_home SDK(Flutter 层)"]
Base["BleBaseManager<br/>invokeMethod 桥接"]
Const["BleMethodConstants<br/>方法名常量"]
end
subgraph sg_Native["原生平台"]
Native["MethodChannel 原生实现<br/>(Android/iOS SDK)"]
BleDev["BLE 设备(固件)"]
end
Page -->|"getDeviceInfo / convert"| DM
DM --> BDM
Page --> BDM
Page --> BSM
BDM -->|"invokeMethod(methodGetDeviceInfo)"| Base
BSM --> Base
Base --> Const
Base -->|"方法调用与结果回传"| Native
Native <-->|"BLE 协议通信"| BleDev
架构说明:页面层只依赖管理器公开的静态方法;管理器把所有调用收敛到 BleBaseManager.invokeMethod,方法名统一来自 BleMethodConstants;原生层负责实际 BLE 通信并回传 Map/List 结果。DeviceInfoManager 是示例 App 侧的纯 Dart 转换层,不参与平台桥接。
主要模块与实现分析
BleDeviceInfoManager — 设备信息管理器
BleDeviceInfoManager(ble_device_info_manager.dart)是 SDK 对外暴露的设备信息查询入口,全部为静态方法,内部统一通过 BleBaseManager.invokeMethod 发起平台调用。
| 方法 | 平台方法常量 | 返回类型 | 说明 |
|---|---|---|---|
getCurrentDeviceType() | methodCurrentDeviceType | Future<int> | 当前设备类型 |
getDeviceInfo() | methodGetDeviceInfo | Future<Map<String, dynamic>> | 完整设备信息(含按键/LED 配置) |
getCardBottomArray() | methodGetCardBottomArray | Future<List<int>> | 底部卡片类型数组 |
getSupportedFunctions() | methodGetCurrentDeviceFunctions | Future<List<String>> | 当前设备支持的功能列表 |
getFunctionCommon() | methodFunctionCommon | Future<void> | 获取通用功能模式 |
关键实现细节:
- 严格失败策略(getDeviceInfo):当平台返回
null时直接抛出Exception('Failed to get device info'),而不是返回空 Map。设计意图是让上层尽早感知"信息不可用"这一异常状态,避免用空数据渲染出错误的配置界面。 - 宽松失败策略(getCardBottomArray / getSupportedFunctions):这两个接口在异常或空结果时分别回退到
[0]与[]。设计意图相反——卡片与功能列表属于"可降级展示"的数据,宁可显示默认值也不阻塞页面。 - 结果类型归一:
getDeviceInfo把平台返回的result强制转为Map<String, dynamic>,保证上层拿到的是统一的字符串键 Map。
DeviceInfoManager — 数据转换层(示例 App)
DeviceInfoManager(device_info_manager.dart)位于示例 App 内,解决一个关键痛点:MethodChannel 回传的 Map 键类型是 Object?,且嵌套的 keySettings/ledSettings 元素本身也是 Map<Object?, Object?>,直接渲染会因类型不匹配而失败。
Map<String, dynamic> convertDeviceInfo(Map<Object?, Object?> rawData) {
final converted = <String, dynamic>{};
rawData.forEach((key, value) {
if (key is String) converted[key] = value;
});
if (converted['keySettings'] is List) {
converted['keySettings'] = (converted['keySettings'] as List)
.map((item) => AppUtil.convertMap(item as Map<Object?, Object?>))
.toList();
}
if (converted['ledSettings'] is List) {
converted['ledSettings'] = (converted['ledSettings'] as List)
.map((item) => AppUtil.convertMap(item as Map<Object?, Object?>))
.toList();
}
return converted;
}
Source: device_info_manager.dart
转换逻辑分三步:
- 键类型过滤:只保留
String类型的键,丢弃非字符串键,防止后续_deviceInfo['keySettings']这类索引操作出现类型错误; - keySettings 深转换:将按键配置列表中的每个元素(
Map<Object?, Object?>)用AppUtil.convertMap递归转为Map<String, dynamic>; - ledSettings 深转换:与按键同理,处理 LED 灯效配置列表。
此外该类持有 deviceType 字段,loadDeviceType() 在页面初始化时调用 BleDeviceInfoManager.getCurrentDeviceType() 缓存设备类型,供功能开关的显隐判断使用。
DeviceSettingsPage — 配置页面加载与刷新
DeviceSettingsPage(device_settings_page.dart)是按键/配置设置的 UI 入口。其 _getDeviceInfo 私有方法展示了完整的数据流:
Future<void> _getDeviceInfo({bool silent = false}) async {
final deviceInfo = await BleDeviceInfoManager.getDeviceInfo();
_deviceInfo = _deviceInfoManager.convertDeviceInfo(deviceInfo);
// ... setState 更新 UI
}
Source: device_settings_page.dart
silent 参数控制是否静默刷新(不显示 loading)。页面在三种时机触发刷新:
- 首次进入(
initState流程,silent: false):先loadDeviceType()再_getDeviceInfo(); - 配置操作完成后(如按键设置保存):
.then((_) => _getDeviceInfo(silent: false))重新拉取最新配置; - 设置变更后静默同步:部分操作使用
silent: true,不打断用户操作。
按键设置列表的渲染入口位于 _deviceInfo['keySettings']:
if (_deviceInfo['keySettings'] != null) {
List<Map<String, dynamic>> keySettings =
List<Map<String, dynamic>>.from(_deviceInfo['keySettings']!);
// ... 构建按键项 UI
}
Source: device_settings_page.dart
页面还通过 BleDeviceSettingManager(ble_device_setting_manager.dart)下发配置修改指令,并在回调后刷新信息;UI 构件由 device_settings_ui_builder.dart 按设备类型动态生成。
BleMethodConstants — 方法名常量
平台桥接方法名统一收敛在 ble_method_constants.dart,例如:
/// 获取设备信息
static const String methodGetDeviceInfo = 'getDeviceInfo';
Source: ble_method_constants.dart
集中管理常量避免了字符串散落在各处导致拼写错误,并让原生端与 Dart 端共享同一份方法名契约。
核心流程
设备信息加载时序
sequenceDiagram
participant Page as DeviceSettingsPage
participant DM as DeviceInfoManager
participant BDM as BleDeviceInfoManager
participant Base as BleBaseManager
participant Native as 原生 SDK / BLE
Page->>DM: loadDeviceType()
DM->>BDM: getCurrentDeviceType()
BDM->>Base: invokeMethod(methodCurrentDeviceType)
Base->>Native: MethodChannel 调用
Native-->>Base: int deviceType
Base-->>BDM: int
BDM-->>DM: int
DM-->>Page: 缓存 deviceType
Page->>BDM: getDeviceInfo()
BDM->>Base: invokeMethod(methodGetDeviceInfo)
Base->>Native: MethodChannel 调用
Native->>Native: 与 BLE 固件交互,组装 keySettings/ledSettings
Native-->>Base: Map<Object?, Object?>
Base-->>BDM: Map
BDM-->>Page: Map<String, dynamic>
Page->>DM: convertDeviceInfo(raw)
DM->>DM: 过滤 String 键 + 深转换 keySettings/ledSettings
DM-->>Page: 统一 Map<String, dynamic>
Page->>Page: setState 渲染配置界面
Note over Page,Native: 配置修改后 .then((_) => _getDeviceInfo(silent: false)) 重新拉取
时序要点:整个链路是单向请求-响应模式,无长连接推送。任何一次配置变更后都必须显式重新调用 getDeviceInfo 才能拿到最新状态,这是示例 App 中"修改→刷新"回调模式(_getDeviceInfo(silent: false))存在的根本原因。
失败回退流程
getDeviceInfo返回null→ 抛Exception→ 页面捕获后按错误处理(不渲染空配置);getCardBottomArray异常 → 回退[0],页面按默认卡片渲染;getSupportedFunctions异常 → 回退[],功能列表为空时页面隐藏相关入口;- 设备断开(
_handleDisconnection)→ 触发_getDeviceInfo(silent: true)兜底刷新,避免界面停留在已断开设备的状态。
使用示例
示例一:查询并转换设备信息(核心链路)
static Future<Map<String, dynamic>> getDeviceInfo() async {
final result = await BleBaseManager.invokeMethod(
BleMethodConstants.methodGetDeviceInfo,
);
if (result != null) {
return Map<String, dynamic>.from(result);
} else {
throw Exception('Failed to get device info');
}
}
Source: ble_device_info_manager.dart
示例二:加载设备类型缓存
Future<void> loadDeviceType() async {
deviceType = await BleDeviceInfoManager.getCurrentDeviceType();
}
Source: device_info_manager.dart
示例三:底部卡片数组的容错读取
static Future<List<int>> getCardBottomArray() async {
try {
final result = await BleBaseManager.invokeMethod(
BleMethodConstants.methodGetCardBottomArray,
);
if (result is List) {
return result.cast<int>();
}
return [0];
} catch (e) {
return [0];
}
}
Source: ble_device_info_manager.dart
示例四:支持的功能列表查询(异常时返回空列表)
static Future<List<String>> getSupportedFunctions() async {
try {
final result = await BleBaseManager.invokeMethod(
BleMethodConstants.methodGetCurrentDeviceFunctions,
);
return List<String>.from(result as List);
} on Exception {
return [];
}
}
Source: ble_device_info_manager.dart
API Reference
BleDeviceInfoManager(静态方法)
static Future<int> getCurrentDeviceType()
获取当前连接的设备类型,返回值为设备类型 ID。由页面/管理器在初始化时调用并缓存。
- Returns:
int设备类型 - Throws: 平台调用异常(由
BleBaseManager.invokeMethod透传)
static Future<Map<String, dynamic>> getDeviceInfo()
获取完整设备信息,包含 keySettings(按键设置)、ledSettings(LED 设置)等配置数据。
- Returns:
Map<String, dynamic>设备信息 - Throws:
Exception('Failed to get device info')— 当平台返回null时抛出
static Future<List<int>> getCardBottomArray()
获取底部卡片类型数组。
- Returns:
List<int>;平台结果不是 List 或调用异常时回退为[0]
static Future<List<String>> getSupportedFunctions()
获取当前设备支持的功能名列表。
- Returns:
List<String>;异常时返回[]
static Future<void> getFunctionCommon()
请求设备返回通用功能模式(单向触发,不解析返回值)。
- Returns:
Future<void>
DeviceInfoManager(示例 App)
Future<void> loadDeviceType()
调用 BleDeviceInfoManager.getCurrentDeviceType() 并缓存到 deviceType 字段。
Map<String, dynamic> convertDeviceInfo(Map<Object?, Object?> rawData)
把 MethodChannel 原始返回值转换为 UI 可直接消费的 Map<String, dynamic>:
- 过滤非
String键; - 对
keySettings与ledSettings列表逐元素执行AppUtil.convertMap深转换。
方法常量与配置
平台桥接方法名常量(定义于 BleMethodConstants,ble_method_constants.dart):
| 常量名 | 字符串值 | 用途 |
|---|---|---|
methodCurrentDeviceType | currentDeviceType | 获取设备类型 |
methodGetDeviceInfo | getDeviceInfo | 获取设备信息(含按键/LED 配置) |
methodGetCardBottomArray | getCardBottomArray | 底部卡片数组 |
methodGetCurrentDeviceFunctions | getCurrentDeviceFunctions | 设备支持的功能列表 |
methodFunctionCommon | functionCommon | 通用功能模式 |
设备信息 Map 中与配置相关的键:
| 键名 | 类型 | 说明 |
|---|---|---|
keySettings | List<Map<String, dynamic>> | 按键事件 → 动作映射表,页面据此渲染按键设置项 |
ledSettings | List<Map<String, dynamic>> | LED 灯效配置表 |
失败模式、边界情况与并发
- 平台返回 null:
getDeviceInfo抛Exception,调用方(页面_getDeviceInfo)依赖 try/catch 处理,silent: false时展示加载错误提示。 - 类型不匹配:MethodChannel 回传的嵌套 Map 键为
Object?,若跳过convertDeviceInfo直接访问_deviceInfo['keySettings']会触发类型错误——转换层是必经之路。 - 降级默认值:
getCardBottomArray失败回退[0]、getSupportedFunctions失败回退[],保证页面可降级渲染而非崩溃。 - 设备断开竞态:连接状态变化与
getDeviceInfo异步返回可能交错;页面在断连回调中触发_getDeviceInfo(silent: true)兜底刷新,避免残留旧设备数据。 - 重复刷新:
getDeviceInfo是无状态查询,多次调用安全;但页面以"修改→刷新"模式工作,高频配置操作会带来多次平台调用,silent参数用于抑制 UI 抖动。 - 并发注意:
BleDeviceInfoManager全部为静态方法且无内部共享可变状态(DeviceInfoManager.deviceType是唯一的页面级缓存),因此不存在多实例状态污染问题;但同一时刻多个页面并发调用平台通道时,结果按 await 顺序各自归位,页面需自行校验结果归属(实践中由单连接管理器串行化)。
扩展点
- 新增设备信息字段:原生端在
getDeviceInfo返回值中增加键,Dart 侧无需改动管理器;如需 UI 展示,在convertDeviceInfo中补充深转换逻辑并在device_settings_ui_builder.dart中按deviceType添加对应构件。 - 新增配置下发:参照
BleDeviceSettingManager的模式,新增静态方法 + 平台方法常量,页面修改后复用_getDeviceInfo(silent: false)刷新。 - 多设备类型适配:
deviceType字段与DeviceSettingsUiBuilder的组合是当前扩展多固件 UI 的既定路径。
Related Links
- BleDeviceInfoManager 源码
- DeviceInfoManager 源码
- DeviceSettingsPage 源码
- BleMethodConstants 源码
- BleDeviceSettingManager 源码
- 相关能力页:连接管理、OTA 升级、音乐播放、灯光设置、闹钟管理