接口文档与收发说明
杰理 OTA 升级(Flutter)收发接口介绍:本文档系统说明 JL_OTA Flutter 插件中 Dart 层与原生平台(Android/iOS)之间通过 MethodChannel 发送指令、通过 EventChannel 接收设备事件的完整接口机制、数据模型、常量定义与调用流程。
Purpose and Scope
本页面覆盖 JL_OTA Flutter 插件的收发(send/receive)接口层,包括:
- Dart 发送层
BleMethod的 MethodChannel 机制与全部接口方法; - 接收层事件流(
BleEventStream)与事件常量(BleEventConstants)的组织方式; - 扫描设备(
ScanDevice)与设备连接(DeviceConnection)等数据模型; - 收发接口的调用流程、平台差异、失败模式与扩展方式。
本页面不涉及具体 OTA 升级协议帧的解析细节、固件包的生成逻辑以及原生 Android/iOS 端 SDK 的内部实现——这些属于插件内部实现,不在本接口文档范围内。升级流程的业务编排(如 JLOtaManager 等上层封装)若存在独立文档,请参见对应页面。
Overview
杰理 OTA 升级 APP 是一款专为使用杰理芯片的设备设计的在线升级工具,允许用户通过蓝牙对设备进行固件升级,以确保设备始终拥有最新的功能和安全修复。从接口层面看,整个插件遵循典型的 Flutter 平台通道(Platform Channel) 架构:
- 发送方向(Dart → Native):Dart 层通过静态类
BleMethod调用MethodChannel的invokeMethod,将"开始扫描 / 停止扫描 / 连接设备 / 断开设备 / 设置通讯方式"等命令发送到原生 SDK; - 接收方向(Native → Dart):原生 SDK 将扫描结果、连接状态、OTA 进度等事件通过事件通道推送回 Dart 层,由
BleEventStream统一分发; - 常量与模型:方法名、参数名、事件名统一收敛在
BleMethodConstants/BleEventConstants中,收发双端共用,避免魔法字符串散落各处。
当前文档对应的 SDK 版本为 V1.1.0(2026/07/03),相比 V1.0.0 增加了复用空间特殊升级流程、单备份 OTA 自动回连 BLE、Gatt Over BR/EDR 连接方式与自定义命令等能力(Android 与 iOS 两端各有侧重)。
flowchart TD
subgraph sg_Dart["Dart 层 (Flutter Plugin)"]
BleMethod["BleMethod<br/>静态发送接口"]
BleEventStream["BleEventStream<br/>事件接收流"]
Models["数据模型<br/>ScanDevice / DeviceConnection"]
Constants["常量<br/>BleMethodConstants / BleEventConstants"]
end
subgraph sg_Native["原生平台"]
Android["Android SDK<br/>com.jieli.ble_plugin"]
IOS["iOS SDK<br/>CoreBluetooth / Gatt Over BR/EDR"]
end
subgraph sg_Device["设备端"]
Device["杰理芯片 BLE 设备"]
end
User["业务 App (调用方)"] -->|"invokeMethod 命令"| BleMethod
BleMethod -->|"MethodChannel<br/>com.jieli.ble_plugin/methods"| Android
BleMethod -->|"MethodChannel"| IOS
Android -->|"EventChannel 事件回调"| BleEventStream
IOS -->|"EventChannel 事件回调"| BleEventStream
BleEventStream -->|"监听/分发"| User
BleMethod --> Models
BleEventStream --> Models
BleMethod --> Constants
BleEventStream --> Constants
Android -->|"BLE GATT 连接"| Device
IOS -->|"BLE GATT 连接"| Device
如上图所示,收发接口是整个插件对外的唯一契约面:业务层只依赖 BleMethod(发)与 BleEventStream(收)两个入口,原生实现细节被完全封装在平台侧。这种设计使得上层业务可以同时兼容 Android / iOS 两套底层 SDK,也便于后续替换或扩展原生实现而不影响业务代码。
说明:图中接收层与模型、常量的文件均位于
code/JL_OTA/lib/目录(如 ble_event_stream.dart、model/scan_device.dart),具体接口签名以官方接口文档为准。
收发通道机制
发送通道:MethodChannel
Dart 发送层的核心是唯一一个静态 MethodChannel 实例,通道名为 com.jieli.ble_plugin/methods。所有"命令型"接口(扫描、连接、断开、设置参数等)都通过该通道下发,属于 Fire-and-forget + 异步回执 模式:invokeMethod 返回的 Future 在原生侧完成处理后 resolve,若原生侧抛出 PlatformException 则在 Dart 侧以异常形式向上传播。
static const MethodChannel _methodChannel = MethodChannel(
'com.jieli.ble_plugin/methods',
);
来源:Jieli OTA Upgrade (Flutter) Send:Receive Interface Introduction.md
选择 MethodChannel 而非 BasicMessageChannel 的意图在于:命令接口天然具有"方法名 + 参数 + 返回值 + 异常"的 RPC 语义,invokeMethod 直接映射为原生方法调用,能获得类型化的参数传递与异常传播,业务代码只需 try/catch 即可处理原生错误。
接收通道:EventChannel 事件流
接收方向与发送方向相反:扫描结果、连接状态变化、OTA 升级进度等事件型数据由原生 SDK 主动推送。插件将这些事件收敛到 BleEventStream(位于 ble_event_stream.dart),业务层通过订阅事件流获取设备状态;事件名称统一定义在 ble_event_constants.dart,与 BleMethodConstants(ble_method_constants.dart)分别管理收发两个方向的常量。
事件驱动与命令驱动的分离是这套接口设计的关键决策:蓝牙设备的连接/断开/升级进度天然是异步的,原生侧无法在 invokeMethod 的同步返回中表达这些持续变化,因此必须依赖事件回调;而业务层订阅事件流后,可以统一处理"用户主动操作"与"设备被动变化"两类信号。
常量收敛
方法名、参数键、事件名全部收敛为常量类,收发双端共用同一套字符串约定。这样做的收益有三点:编译期可发现拼写错误、便于原生侧与 Dart 侧对照维护、避免魔法字符串在业务代码中泛滥。例如发送层的通道名、METHOD_START_SCAN、METHOD_CONNECT_DEVICE、ARG_INDEX 等均出自 BleMethodConstants。
发送层接口清单(BleMethod)
以下接口均来自官方接口文档第 1 章"Dart 的发送层的接口(ble_method.dart)",为静态异步方法,统一遵循 try { await _methodChannel.invokeMethod(...) } on PlatformException catch (e) { ...; rethrow; } 的错误处理模式。
初始化与扫描控制
static const MethodChannel _methodChannel = MethodChannel(
'com.jieli.ble_plugin/methods',
);
static Future<void> startScan() async {
try {
await _methodChannel.invokeMethod(BleMethodConstants.METHOD_START_SCAN);
} on PlatformException catch (e) {
print("Failed to start scan: ${e.message}");
rethrow;
}
}
使用示例:await BleMethod.startScan();
来源:Jieli OTA Upgrade (Flutter) Send:Receive Interface Introduction.md
startScan() 触发原生 BLE 扫描,扫描结果通过接收层事件流回传,而非在方法返回值中一次性给出——这是异步事件模型的典型应用。stopScan() 与之对称,调用 METHOD_STOP_SCAN 停止扫描。
连接与断开
static Future<void> connectDevice(int index) async {
try {
await _methodChannel.invokeMethod(
BleMethodConstants.METHOD_CONNECT_DEVICE,
{BleMethodConstants.ARG_INDEX: index},
);
} on PlatformException catch (e) {
print("Failed to connect device at index $index: ${e.message}");
rethrow;
}
}
使用示例:
/// Connect to a device at the specified index
void _connectToDevice(int index) async {
try {
await BleMethod.connectDevice(index);
} catch (e) {
log("Failed to connect to device: $e");
// Optionally show an error message to the user
}
}
来源:Jieli OTA Upgrade (Flutter) Send:Receive Interface Introduction.md
connectDevice(int index) 的参数是扫描结果列表中的设备索引而非设备地址——索引指向原生侧缓存的扫描结果数组,ARG_INDEX 作为参数键传递。断开接口 disconnectBtDevice(int index) 使用 METHOD_DISCONNECT_BT_DEVICE,同样按索引操作。索引方案的取舍:原生侧持有扫描结果数组,索引传递避免了大对象(完整设备信息)跨通道序列化的开销;但这也要求业务层在调用连接前必须保证扫描列表与原生侧一致(例如扫描尚未完成或列表已被刷新时索引可能失效)。
通讯方式配置(平台差异)
发送层暴露了一组"读取/设置"配对接口,用于配置底层蓝牙连接方式,Android 与 iOS 的配置项不同:
- Android:
getConnectWay()/setConnectWay(int connectWay),对应METHOD_GET_CONNECT_WAY/METHOD_SET_CONNECT_WAY,切换 BLE 等通讯方式(示例中AppConstants.communicationWayBle为 BLE 方式的常量值); - iOS:
isUseSdkBluetooth()/setConnectUsingSdkBluetooth(bool),控制是否使用 SDK 蓝牙连接;isUseGattOverEdr()/setGattOverEdrState(bool),控制 Gatt Over BR/EDR 连接方式(V1.1.0 新增);getGattServiceUuids()/setGattServiceUuids(List<String>)读取/设置 GATT Service UUID 列表。
static Future<void> setConnectWay(int connectWay) async {
try {
await _methodChannel.invokeMethod(BleMethodConstants.METHOD_SET_CONNECT_WAY, {
BleMethodConstants.ARG_CONNECT_WAY: connectWay,
});
} on PlatformException catch (e) {
print("Failed to set BLE way: ${e.message}");
rethrow;
}
}
使用示例:
int communicationMethod = AppConstants.communicationWayBle;
await BleMethod.setConnectWay(communicationMethod);
来源:Jieli OTA Upgrade (Flutter) Send:Receive Interface Introduction.md
static Future<bool> isUseSdkBluetooth() async {
try {
return await _methodChannel.invokeMethod(
BleMethodConstants.METHOD_IS_USING_SDK_BLUETOOTH,
) ??
true;
} on PlatformException catch (e) {
print("Failed to check if sdk bluetooth is used: ${e.message}");
rethrow;
}
}
使用示例:await BleMethod.isUseSdkBluetooth();
来源:Jieli OTA Upgrade (Flutter) Send:Receive Interface Introduction.md
值得注意的是读取类接口的返回值处理:invokeMethod 返回 null 时用 ?? true / ?? [] 提供安全默认值,避免原生侧未实现或尚未初始化时业务层拿到空值崩溃。getGattServiceUuids() 在异常时则直接返回空列表 [] 而非 rethrow——这一差异说明该接口被视为"可降级"的能力:UUID 列表为空时上层可自行回退到默认 Service,而连接/扫描等核心命令失败必须向上抛出让业务层感知。
核心收发流程
一次典型的 OTA 升级会话,从接口视角看由以下收发步骤串联:扫描 → 连接 → 配置通讯方式 → 升级数据收发 → 断开。发送命令均由 BleMethod 发起,设备状态与升级进度通过 BleEventStream 回传。
sequenceDiagram
participant App as 业务 App
participant BM as BleMethod (Dart)
participant Native as 原生 SDK (Android/iOS)
participant Device as 杰理 BLE 设备
participant ES as BleEventStream (Dart)
App->>BM: startScan()
BM->>Native: METHOD_START_SCAN
Native->>Device: BLE 广播扫描
Device-->>Native: 广播包
Native-->>ES: 扫描到设备事件
ES-->>App: 设备列表 (ScanDevice)
App->>BM: connectDevice(index)
BM->>Native: METHOD_CONNECT_DEVICE + ARG_INDEX
Native->>Device: GATT 连接
Device-->>Native: 连接建立
Native-->>ES: 连接状态事件
ES-->>App: DeviceConnection 状态
alt 需要切换通讯方式
App->>BM: setConnectWay(communicationWayBle)
BM->>Native: METHOD_SET_CONNECT_WAY
end
App->>BM: OTA 数据下发命令
BM->>Native: MethodChannel 命令
Native->>Device: 固件数据写入
Device-->>Native: 升级进度
Native-->>ES: 进度事件
ES-->>App: 进度回调
App->>BM: disconnectBtDevice(index)
BM->>Native: METHOD_DISCONNECT_BT_DEVICE
Native->>Device: 断开连接
流程要点:
- 扫描阶段:
startScan()只负责"开启扫描"这一命令,设备发现结果完全依赖事件流异步回传,业务层需要在事件回调中增量构建设备列表; - 连接阶段:
connectDevice(index)按索引寻址设备,连接结果(成功/失败)同样通过事件回调通知,invokeMethod的 Future 只代表"命令已被原生接受执行"; - 配置阶段:按平台差异设置通讯方式(Android 的
setConnectWay、iOS 的 SDK 蓝牙 / Gatt Over BR/EDR 开关),这些配置必须在连接建立或升级开始前完成; - 升级阶段:固件数据的发送与进度反馈构成高频收发循环,进度事件是业务层 UI 更新的唯一数据源;
- 断开阶段:
disconnectBtDevice(index)显式释放连接,V1.1.0 起单备份 OTA 支持自动回连 BLE,回连行为由原生侧触发并通过事件流通知上层。
数据模型与常量
数据模型
code/JL_OTA/lib/model/ 目录定义了收发接口两侧共享的数据结构:
| 文件 | 职责 |
|---|---|
| model/scan_device.dart | 扫描结果模型,承载设备名、MAC/地址、信号强度等扫描信息;在事件流中被序列化后回传给 Dart 层 |
| model/device_connection.dart | 设备连接状态模型,描述连接建立/断开/错误等连接生命周期状态 |
模型层存在的意义在于:原生事件以 Map/基本类型形式跨通道传输,Dart 侧需要将这些原始数据转换为类型化对象,业务层才能以强类型方式访问字段,避免散落的 map['key'] 取值与类型强转。
常量定义
| 常量类 | 文件 | 覆盖范围 |
|---|---|---|
BleMethodConstants | constant/ble_method_constants.dart | 发送方向:MethodChannel 方法名(METHOD_*)与参数键(ARG_*),如 METHOD_START_SCAN、METHOD_CONNECT_DEVICE、ARG_INDEX、ARG_CONNECT_WAY |
BleEventConstants | constant/ble_event_constants.dart | 接收方向:事件名常量,用于 BleEventStream 的事件类型判别 |
Constants / AppConstants | constant/constants.dart | 业务常量,如通讯方式枚举值 communicationWayBle 等 |
常量收敛保证了 Dart 侧与原生侧对同一字符串约定的单一事实来源:任何一方修改方法名/事件名,另一方只需对照常量类同步修改即可,且 Dart 侧的拼写错误会在编译期暴露。
接收层事件流(BleEventStream)
与发送层的命令式 API 相对,接收层 ble_event_stream.dart 是事件的汇聚点:
- 原生 SDK 通过 EventChannel 推送的事件(扫描结果、连接状态、升级进度等)在此统一接收;
- 事件类型依据
BleEventConstants中的事件名进行判别与分发; - 业务层通过订阅该事件流获取设备状态变化,实现"命令驱动 + 事件驱动"的完整闭环。
设计意图:将事件订阅点收敛为单一入口,业务层无需关心原生 EventChannel 的细节,也便于在事件流层统一做类型转换、日志埋点与错误兜底。事件流的典型消费模式为:BleEventStream 暴露一个 Stream/回调注册接口,业务层在页面生命周期内订阅、在销毁时取消订阅,避免重复订阅导致的事件重复处理。
API 参考(发送层方法签名)
以下为官方接口文档第 1 章中已确认的 BleMethod 静态方法签名。所有方法均为 static,通过 await BleMethod.xxx() 调用;除特殊说明外,失败时抛出 PlatformException。
static Future<void> startScan()
启动 BLE 扫描。扫描结果通过事件流异步回传,本方法不返回设备列表。
参数: 无 返回: Future<void>,原生接受命令后 resolve 异常: PlatformException(原生侧启动扫描失败)
static Future<void> stopScan()
停止 BLE 扫描。
参数: 无 返回: Future<void>异常: PlatformException
static Future<void> connectDevice(int index)
按扫描列表索引连接设备。
参数:
index(int):设备在原生侧扫描结果数组中的索引(来自ARG_INDEX)
返回: Future<void>,命令被原生接受后 resolve;连接结果(成功/失败)通过事件流回调 异常: PlatformException
static Future<void> disconnectBtDevice(int index)
按索引断开与设备的连接。
参数:
index(int):设备索引(ARG_INDEX)
返回: Future<void>异常: PlatformException
static Future<int> getConnectWay()(Android)
读取当前使用的通讯方式。
返回: Future<int>,通讯方式枚举值;原生返回 null 时兜底为 true(1) 异常: PlatformException
static Future<void> setConnectWay(int connectWay)(Android)
设置当前使用的通讯方式(如 AppConstants.communicationWayBle)。
参数:
connectWay(int):通讯方式枚举值(ARG_CONNECT_WAY)
返回: Future<void>异常: PlatformException
static Future<bool> isUseSdkBluetooth()(iOS)
读取是否使用 SDK 蓝牙连接。
返回: Future<bool>,null 时兜底为 true异常: PlatformException
static Future<void> setConnectUsingSdkBluetooth(bool isUsingSDKBluetooth)(iOS)
设置是否使用 SDK 蓝牙连接。
参数:
isUsingSDKBluetooth(bool):ARG_IS_USING_SDK_BLUETOOTH
返回: Future<void>异常: PlatformException
static Future<bool> isUseGattOverEdr()(iOS,V1.1.0 新增)
读取是否使用 Gatt Over BR/EDR 连接方式。
返回: Future<bool>,null 时兜底为 true异常: PlatformException
static Future<void> setGattOverEdrState(bool gattOverEdrState)(iOS,V1.1.0 新增)
设置 Gatt Over BR/EDR 连接方式开关。
参数:
gattOverEdrState(bool):ARG_IS_USING_GATT_OVER_EDR
返回: Future<void>异常: PlatformException
static Future<List<String>> getGattServiceUuids()(iOS)
读取 GATT Service UUID 列表。
返回: Future<List<String>>,异常时返回空列表 [](可降级接口,不 rethrow) 异常: 不抛出(异常被吞掉并返回空列表)
static Future<void> setGattServiceUuids(List<String> uuids)(iOS)
设置 GATT Service UUID 列表。
参数:
uuids(List<String>):ARG_GATT_SERVICE_UUIDS
返回: Future<void>异常: PlatformException
说明:接口文档第 1.12 节展示了
getGattServiceUuids()的读取实现(含List<String>.from(result ?? [])的类型安全转换);setGattServiceUuids对应文档第 1.13 节起的内容。文档后续章节(共 1167 行)还包含接收层事件说明与更多接口,完整内容请查阅原文。
失败模式、边界情况与并发
统一的 PlatformException 处理
所有发送接口都遵循相同的异常约定:原生侧错误被包装为 PlatformException,Dart 侧 print 日志后 rethrow,把错误决策权交还给业务层。唯一例外是 getGattServiceUuids() 返回空列表——这是刻意的"可降级"设计:UUID 列表只是连接参数的补充信息,缺失时上层可回退到默认 GATT Service,不影响核心流程。
索引失效风险
connectDevice(index) / disconnectBtDevice(index) 以索引寻址设备,存在两类边界风险:
- 扫描列表刷新:重新扫描会重建原生侧设备数组,旧索引可能指向错误设备或越界;
- 时序竞态:
startScan()是异步命令,若业务层在扫描事件尚未回传时立即connectDevice(0),可能命中空列表。
建议业务层始终以事件流回调为准来构建设备列表与索引映射,并在连接前校验索引有效性。
异步语义的两种返回
必须区分两类"返回":invokeMethod 的 Future 仅代表命令已被原生接受,而操作结果(连接成功与否、扫描到哪些设备)永远通过事件流异步到达。若业务层把命令 Future 当作结果来用,会出现状态不同步。
并发与重复调用
方法通道按调用顺序串行投递到原生侧,但 Dart 侧不保证业务层的多次 invokeMethod 之间存在状态互斥。例如连续两次 connectDevice 的行为由原生 SDK 决定;业务层应通过状态机(未连接 → 连接中 → 已连接)自行串行化关键操作,避免重复扫描、重复连接。
平台差异边界
| 能力 | Android | iOS |
|---|---|---|
| 通讯方式读写 | getConnectWay / setConnectWay | —(使用 SDK 蓝牙开关替代) |
| SDK 蓝牙开关 | — | isUseSdkBluetooth / setConnectUsingSdkBluetooth |
| Gatt Over BR/EDR | —(V1.1.0 支持) | isUseGattOverEdr / setGattOverEdrState(V1.1.0) |
| GATT Service UUID 配置 | — | getGattServiceUuids / setGattServiceUuids |
| 复用空间特殊升级 | ✅(V1.1.0) | — |
| OTA 自动回连 BLE | ✅(V1.1.0,单备份) | ✅(V1.1.0 修复回连超时) |
调用平台专属接口前,应先用 Platform.isAndroid / Platform.isIOS 做平台判断,或确保接口在另一平台上是安全的 no-op,避免跨平台调用崩溃。
性能与运维注意事项
高频收发路径
OTA 升级阶段存在固件数据写入与进度事件的高频往返。跨 MethodChannel/EventChannel 的序列化(Dart ↔ 原生 ↔ BLE GATT)是这条路径的主要开销来源,因此:
- 固件分块大小与发送间隔由原生 SDK 控制,Dart 层不应在业务代码中额外插入不必要的异步延迟;
- 进度事件属于高频小包,事件流订阅方应避免在回调中执行耗时操作(如磁盘写入、复杂 UI 构建),可考虑节流(throttle)后刷新进度条;
- 若升级中伴随其他业务逻辑,应避免在升级期间频繁调用扫描/连接类命令,减少对 GATT 通道的干扰。
生命周期管理
接收层事件流订阅必须与页面/服务生命周期绑定:进入页面时订阅、离开页面或升级结束时取消订阅。否则在多次进入页面后会出现重复回调、事件堆积甚至内存泄漏。原生侧的 EventChannel 在 Dart 侧订阅取消后即停止推送。
日志与排查
发送层接口在失败时统一打印 Failed to ...: ${e.message} 日志。线上排查建议:
- 先核对命令是否被原生接受(
invokeMethod是否抛异常); - 再确认事件流是否收到对应回调(连接/进度事件);
- 结合原生 SDK 日志确认 BLE 链路状态(广播、GATT 连接、MTU 协商)。
扩展点
新增命令型接口
若需要新增发送命令,遵循三步扩展路径:
- 在
BleMethodConstants(constant/ble_method_constants.dart)中新增METHOD_*与ARG_*常量; - 在
BleMethod中新增静态方法,复用_methodChannel.invokeMethod+PlatformException处理模板; - 原生侧(Android/iOS SDK 插件)注册同名方法处理并回传结果。
新增事件类型
接收方向同理:在 BleEventConstants(constant/ble_event_constants.dart)中新增事件名,原生侧通过 EventChannel 推送,BleEventStream 增加对应分支分发。
平台能力扩展
V1.1.0 的迭代方向(Gatt Over BR/EDR、自定义命令、自动回连)表明该接口层刻意保持"命令 + 事件"的稳定骨架,新能力以新增 METHOD_* / 事件名的方式叠加,而不改变既有接口签名——这是对上层业务兼容性的承诺。业务侧新增能力时同样应优先新增方法而非修改既有方法语义。
版本演进
| 版本 | 日期 | 主要变更 |
|---|---|---|
| V1.1.0 | 2026/07/03 | Android:复用空间特殊升级流程、单备份 OTA 自动回连 BLE、Gatt Over BR/EDR、自定义命令;iOS:修复 OTA 回连超时、Gatt Over BR/EDR、自定义命令 |
| V1.0.0 | 2025/11/19 | 初始版本发布 |
版本记录来自接口文档头部版本表(原文第 3-6 行)。升级 SDK 时建议逐版本核对变更项,尤其关注平台专属能力(如自动回连、BR/EDR)是否需要业务侧新增配置调用。
相关链接
- 官方接口文档原文:Jieli OTA Upgrade (Flutter) Send:Receive Interface Introduction.md(英文版见 Send:Receive Interface Introduction_en.md)
- 发送层实现:code/JL_OTA/lib/ble_method.dart
- 接收层实现:code/JL_OTA/lib/ble_event_stream.dart
- 常量定义:ble_method_constants.dart / ble_event_constants.dart / constants.dart
- 数据模型:model/scan_device.dart / model/device_connection.dart
- 插件总览:code/JL_OTA/README.md
- 仓库根说明:README.md
说明:本文档中接收层事件流、模型与常量的文件级描述基于
code/JL_OTA/lib/目录结构归纳;具体的BleEventStream事件名与签名以官方接口文档原文第 2 章及后续章节为准。