自动回连机制
OTA 升级过程中,设备在切换广播地址(旧地址 → 新 OTA 地址)或临时断链后,由 SDK 自动扫描、识别并重新建立蓝牙连接的一套跨端机制。该机制由 Flutter 层的 MethodChannel 接口、Android 层的 ReConnectHelper 任务调度器以及 iOS 层的 JLOtaReConnectOption 配置模型共同实现。
Purpose and Scope
本文档讲解 JL_OTA_Flutter 中「自动回连」能力的完整实现,覆盖:
- Flutter/Dart 侧的接口暴露方式(
ble_method.dart中的自定义回连开关); - Android 侧的核心任务调度器
ReConnectHelper的内部机制(参数管理、广播缓存、超时控制、扫描-连接状态机); - Android 侧的 OTA 回连状态模型
OTAReconnect; - iOS 侧的回连选项配置模型
JLOtaReConnectOption(服务/特征 UUID、认证秘钥、写响应方式等)。
本页聚焦于「连接层」的自动回连逻辑;OTA 升级本身的流程控制(固件推送、校验、状态机迁移)属于升级主流程,不在本页展开。蓝牙扫描底层 API(BleManager、startLeScan 等)的具体实现请参见对应蓝牙管理页。
Overview
为什么需要自动回连
杰理(Jieli)芯片的 OTA 升级存在一个关键特性:设备在进入升级模式后,通常会改变其广播内容甚至蓝牙地址(通过新 ADV 广播携带旧地址信息,标识 OTA_IDENTIFY)。若 SDK 只按升级前的地址连接,升级过程中一旦断链就无法再次找到设备。自动回连机制正是为此设计:
- 地址漂移适配:通过 OTA 广播中的
oldBleAddress将新地址映射回旧地址,确保升级前后的设备被识别为同一目标; - 断链自愈:连接断开后自动重新扫描并回连,无需用户手动操作;
- 超时保护:回连任务有总超时(约 65 秒),避免无限期扫描消耗电量;
- 自定义回连开关:允许上层(Flutter)选择使用 SDK 内置回连还是自行处理回连。
关键概念
| 概念 | 说明 |
|---|---|
ReconnectParam | 一次回连任务的参数:目标设备地址 + 是否使用新 ADV 方式 |
mBleAdvCache | 扫描到的 OTA 广播缓存,用于新地址 → 旧地址映射 |
MSG_RECONNECT_TIMEOUT | 回连总超时消息(65 秒),触发后停止扫描并清空任务 |
MSG_PROCESS_TASK | 处理回连任务的消息,驱动扫描-连接状态机 |
JLOtaReConnectOption | iOS 端回连配置:服务/读写特征 UUID、认证、写响应方式 |
isUseCustomReConnectWay | Flutter 侧查询/设置是否使用自定义回连方式的通道方法 |
Architecture
flowchart TD
subgraph sg_Flutter["Flutter 层 (Dart)"]
App["App / 业务代码"]
BM["ble_method.dart<br/>isUseCustomReConnectWay / setUseCustomReConnectWay"]
end
subgraph sg_MethodChannel["MethodChannel"]
MC["平台通道"]
end
subgraph sg_Android["Android 原生层 (Kotlin)"]
RH["ReConnectHelper<br/>回连任务调度器"]
PARAM["ReconnectParam 列表"]
ADV["mBleAdvCache<br/>OTA 广播缓存"]
BMGR["BleManager<br/>startLeScan / connectBleDevice"]
OTAST["OTAReconnect<br/>回连状态模型"]
end
subgraph sg_iOS["iOS 原生层 (Objective-C)"]
OPT["JLOtaReConnectOption<br/>回连选项配置"]
IOSLIB["JL_OTALib<br/>回连处理"]
end
App --> BM
BM -->|"invokeMethod"| MC
MC -->|"Android 实现"| RH
MC -->|"iOS 实现"| OPT
RH --> PARAM
RH --> ADV
RH --> BMGR
RH --> OTAST
OPT --> IOSLIB
架构说明:
- Flutter 层是回连机制的对外窗口:业务方通过
ble_method.dart的静态方法查询/设置「是否使用自定义回连方式」。默认情况下 SDK 使用内置回连,上层无需介入。 - MethodChannel 是跨端桥接层,
METHOD_IS_USE_CUSTOM_RECONNECT_WAY与METHOD_SET_USE_CUSTOM_RECONNECT_WAY分别对应查询与设置两个动作。 - Android 层由
ReConnectHelper承担全部回连逻辑:它持有待回连参数列表mParams与广播缓存mBleAdvCache,通过主线程Handler调度扫描与连接,并通过BleEventCallback监听蓝牙状态、扫描结果与连接结果。回连状态通过OTAReconnect(继承自OTAState,状态码OTA_STATE_RECONNECT)上报给上层。 - iOS 层将回连参数封装为
JLOtaReConnectOption:包括服务 UUID(默认AE00)、写特征 UUID(默认AE01)、读特征 UUID(默认AE02)、是否设备认证、握手秘钥以及写操作是否需要响应。iOS 端 SDK(JL_OTALib)依据这些参数执行回连,设计上比 Android 更偏「配置驱动」。
Android 端核心实现
Android 侧自动回连的全部逻辑集中在 ReConnectHelper(包路径 com.jieli.otasdk.util),它是一个典型的任务队列 + 状态机实现:回连请求被封装为 ReconnectParam 放入列表,由主线程 Handler 逐条驱动扫描与连接。
数据结构与消息调度
ReConnectHelper 维护三块核心状态:待回连参数列表、OTA 广播缓存,以及主线程消息队列。
class ReConnectHelper(private val mContext: Context, private val mBtManager: BleManager) {
private val mParams: MutableList<ReconnectParam> = ArrayList()
private val mBleAdvCache: MutableMap<String, BleScanMessage> = HashMap()
private val mUIHandler = Handler(Looper.getMainLooper()) { msg: Message ->
when (msg.what) {
MSG_RECONNECT_TIMEOUT -> {
stopBtScan()
mParams.clear()
}
MSG_PROCESS_TASK -> processReconnectTask()
else -> if (msg.obj is String) {
val address = msg.obj as String
removeParam(address)
}
}
true
}
Source: ReConnectHelper.kt
设计意图:
- 所有状态变更都在主线程
mUIHandler中串行执行,避免多线程并发修改mParams/mBleAdvCache造成的不一致。蓝牙回调(BleEventCallback)不直接操作列表,而是通过发送消息回到主线程处理。 mParams使用ArrayList<ReconnectParam>,ReconnectParam重写了equals/hashCode(基于deviceAddress与isUseNewADV),因此同一设备的重复回连请求会被去重。mBleAdvCache以扫描到的新地址为 key 缓存BleScanMessage,其oldBleAddress字段是识别「升级前设备」的关键。
回连任务的生命周期
一次回连从 putParam 开始,到 removeParam 结束,整体可抽象为以下状态机:
flowchart TD
Start([断链 / 进入升级]) --> Put["putParam(param)<br/>加入 mParams 并启动超时"]
Put --> First{"isReconnecting?"}
First -->|"否"| Timeout["发送 MSG_RECONNECT_TIMEOUT<br/>RECONNECT_TIMEOUT + 10s"]
First -->|"是"| Skip["等待现有任务结束"]
Timeout --> Proc["processReconnectTask()"]
Skip --> Proc
Proc --> ScanCheck{"isBleScanning?"}
ScanCheck -->|"是"| Delay["延迟 FAILED_DELAY 重试"]
ScanCheck -->|"否"| Sys{"系统已连接设备?"}
Sys -->|"是"| Direct["直接 connectBleDevice"]
Sys -->|"否"| LeScan["startLeScan(SCAN_TIMEOUT)"]
LeScan --> ScanFail{"启动扫描失败?"}
ScanFail -->|"是"| Delay
ScanFail -->|"否"| Discover["onDiscoveryBle<br/>解析 OTA 广播"]
Discover --> Match{"isReconnectDevice?"}
Match -->|"是"| StopScan["stopBtScan()<br/>记录 connectAddress"]
StopScan --> Connect["connectBleDevice(device)"]
Discover -->|"否"| Continue["继续扫描其他设备"]
Connect --> Conn{"onBleConnection"}
Conn -->|"STATE_CONNECTED"| Done["removeParam<br/>回连完成"]
Conn -->|"STATE_DISCONNECTED"| Resume["重新发送 MSG_PROCESS_TASK"]
Done --> End([结束])
Resume --> Proc
各阶段说明:
- 入队(
putParam):校验参数非空后去重加入列表。若当前没有正在进行的回连(isReconnecting == false,即无MSG_RECONNECT_TIMEOUT消息),会先发送一个延迟RECONNECT_TIMEOUT + 10s的总超时消息,再立即发送MSG_PROCESS_TASK开始处理。这里的+10s缓冲保证首次putParam后任务有充足时间完成启动。
fun putParam(param: ReconnectParam?): Boolean {
if (null == param) return false
if (!mParams.contains(param)) {
if (mParams.add(param)) {
//添加任务超时
mUIHandler.sendEmptyMessageDelayed(mParams.hashCode(), RECONNECT_TIMEOUT)
if (!isReconnecting) {
mUIHandler.sendMessageDelayed(
mUIHandler.obtainMessage(
MSG_RECONNECT_TIMEOUT,
param.deviceAddress
), RECONNECT_TIMEOUT + 10 * 1000
)
mUIHandler.sendEmptyMessage(MSG_PROCESS_TASK)
}
return true
}
} else {
return true
}
return false
}
Source: ReConnectHelper.kt
- 任务处理(
processReconnectTask):回连状态机的核心驱动器。优先检查蓝牙是否正在扫描(正在扫描则延迟重试,避免扫描冲突);其次检查系统级已连接设备列表,若目标已在系统连接列表中则跳过扫描直接连接;否则启动一次限定SCAN_TIMEOUT的 LE 扫描。
private fun processReconnectTask() {
if (mBtManager.isBleScanning) {
mUIHandler.sendEmptyMessageDelayed(MSG_PROCESS_TASK, FAILED_DELAY)
return
}
val connectedDevice = systemConnectedDevice
if (null != connectedDevice) {
val param = getCacheParam(connectedDevice.address)
if (null != param) param.connectAddress = connectedDevice.address
mBtManager.connectBleDevice(connectedDevice)
return
}
if (!mBtManager.startLeScan(SCAN_TIMEOUT)) {
JL_Log.i(TAG, "processReconnectTask : start Le scan failed.")
mUIHandler.sendEmptyMessageDelayed(MSG_PROCESS_TASK, FAILED_DELAY)
}
}
Source: ReConnectHelper.kt
- 设备识别(
isReconnectDevice+onDiscoveryBle):扫描到广播后先用ParseDataUtil.parseOTAFlagFilterWithBroad解析 OTA 标识,命中则缓存广播。识别规则分两种模式:- 新 ADV 模式(
isUseNewADV == true):设备广播地址已改变,需要把广播中的oldBleAddress与任务的目标地址比对; - 旧地址模式:直接比对设备地址。 命中后停止扫描、记录实际连接地址
connectAddress并发起连接。
- 新 ADV 模式(
override fun onDiscoveryBle(device: BluetoothDevice?, bleScanMessage: BleScanInfo) {
if (!isReconnecting || null == device) return
val advMsg = ParseDataUtil.parseOTAFlagFilterWithBroad(
bleScanMessage.rawData,
JL_Constant.OTA_IDENTIFY
)
if (advMsg != null) {
mBleAdvCache[device.address] = advMsg
JL_Log.d(TAG, "onDiscoveryBle : put data in map.")
}
val isReconnectDevice = isReconnectDevice(device, advMsg)
if (isReconnectDevice) {
stopBtScan()
val param = getCacheParam(device.address)
if (null != param) param.connectAddress = device.address
mBtManager.connectBleDevice(device)
}
}
Source: ReConnectHelper.kt
结果处理(
onBleConnection):连接成功(STATE_CONNECTED)即视为回连完成,调用removeParam清理任务并移除对应超时消息;若连接断开(STATE_DISCONNECTED)则重新发送MSG_PROCESS_TASK继续回连。超时兜底:
MSG_RECONNECT_TIMEOUT(总时长取DeviceReConnectManager.RECONNECT_TIMEOUT,约 65 秒)触发时停止扫描并清空所有待回连参数,防止无限扫描。蓝牙关闭(onAdapterChange)与扫描结束(onDiscoveryBleChange)也会驱动任务重新调度,实现「蓝牙重新打开后自动恢复回连」。
OTA 回连状态模型
回连结果以状态对象形式上报给 OTA 流程:
class OTAReconnect(device: BluetoothDevice?, var reconnectAddress: String?, var isNewWay: Boolean) :
OTAState(
OTA_STATE_RECONNECT, device
) {
override fun toString(): String {
return "OTAReconnect(reconnectAddress=$reconnectAddress, isNewWay=$isNewWay)"
}
}
Source: OTAReconnect.kt
该模型继承 OTAState,状态码固定为 OTA_STATE_RECONNECT,携带两个回连关键信息:reconnectAddress(实际回连到的地址,可能是新的 OTA 地址)与 isNewWay(是否走新 ADV 方式)。上层收到该状态即可确认设备已重新就绪、可以继续后续 OTA 步骤。
iOS 端实现:JLOtaReConnectOption
iOS 侧的回连采用配置驱动模式:SDK 暴露一个选项对象 JLOtaReConnectOption,业务方填充必要参数后交给 JL_OTALib 执行回连,无需关心底层扫描细节。
@interface JLOtaReConnectOption : NSObject
/// 是否需要使用设备认证
@property (assign, nonatomic) BOOL deviceAuthorize;
/// 握手(配对)秘钥
@property(strong,nonatomic)NSData *__nullable authKey;
/// 设备服务UUID
/// 默认是 AE00
@property (strong, nonatomic) NSString *serviceUUID;
///写特征 UUID
/// 默认是 AE01
@property (strong, nonatomic) NSString *writeUUID;
/// 读特征 UUID
/// 默认是 AE02
@property (strong, nonatomic) NSString *readUUID;
/// 是否需要响应的回应方式
/// 默认是NO
@property (assign, nonatomic) BOOL isWriteWithResponse;
+ (JLOtaReConnectOption *)defaultOption;
@end
Source: JLOtaReConnectOption.h
设计意图:
- UUID 可配置化:
serviceUUID(默认AE00)、writeUUID(默认AE01)、readUUID(默认AE02)允许厂商自定义 GATT 服务与特征,适配不同设备的私有协议; - 安全回连:
deviceAuthorize与authKey支持带认证(配对握手)的回连流程,避免未授权设备接入升级通道; - 写响应策略:
isWriteWithResponse控制写特征时是否等待设备响应,默认NO(WriteWithoutResponse),兼顾吞吐与兼容性——部分老设备固件不支持无响应写入时可开启; defaultOption工厂方法提供一份带默认值的选项,业务方可基于默认值按需覆盖。
Flutter 层桥接接口
Flutter 层通过 MethodChannel 将「是否使用自定义回连方式」的开关暴露给 Dart 业务代码。默认 SDK 走内置回连;当上层希望自行实现回连(例如需要自定义 UI 或特殊配对流程)时,可关闭内置回连。
// 读取是否使用自定义回连方式
static Future<bool> isUseCustomReConnectWay() async {
try {
return await _methodChannel.invokeMethod(
BleMethodConstants.METHOD_IS_USE_CUSTOM_RECONNECT_WAY,
) ??
false;
} on PlatformException catch (e) {
print(
"Failed to check if custom reconnect way is used: ${e.message}"
);
rethrow;
}
}
// 设置是否使用自定义回连方式
static Future<void> setUseCustomReConnectWay(bool isCustom) async {
try {
await _methodChannel.invokeMethod(
BleMethodConstants.METHOD_SET_USE_CUSTOM_RECONNECT_WAY,
{BleMethodConstants.ARG_IS_CUSTOM: isCustom},
);
} on PlatformException catch (e) {
print("Failed to set custom reconnect way: ${e.message}");
rethrow;
}
}
Source: ble_method.dart
实现要点:
- 查询方法对原生返回结果做
?? false兜底,未实现或返回 null 时按「使用内置回连」处理,行为安全; - 设置方法将布尔值封装为
ARG_IS_CUSTOM参数传给原生端; - 两个方法均在
PlatformException时打印日志并rethrow,由上层决定是否降级处理——这种「不吞异常」的做法保证业务方能感知原生侧失败; - 同一套接口在仓库的
libs/ble_method.dart(发布版库)中同步维护,保证 SDK 与示例工程行为一致。
跨端调用链路
sequenceDiagram
participant App as Flutter App
participant BM as ble_method.dart
participant MC as MethodChannel
participant RH as ReConnectHelper (Android)
participant IOS as JL_OTALib (iOS)
App->>BM: setUseCustomReConnectWay(false)
BM->>MC: invokeMethod(METHOD_SET_USE_CUSTOM_RECONNECT_WAY)
MC-->>RH: 原生实现(记录开关状态)
Note over RH: 断链/升级地址切换后<br/>SDK 内部自动启动回连
RH->>RH: putParam(ReconnectParam)
RH->>RH: processReconnectTask → startLeScan
RH->>RH: onDiscoveryBle 匹配 OTA 广播
RH->>RH: connectBleDevice + removeParam
RH-->>App: OTAReconnect 状态上报
Note over IOS: iOS 侧配置驱动
App->>IOS: JLOtaReConnectOption (UUID/认证/写响应)
IOS-->>App: 自动回连完成回调
Usage Examples
Android:注册回连任务
以下代码展示如何使用 ReConnectHelper 注册一个基于新 ADV 方式的回连任务(摘自 SDK 内部调用模式):
val param = ReconnectParam(deviceAddress = targetAddress, isUseNewADV = true)
reConnectHelper.putParam(param)
// 连接成功回调中移除任务
reConnectHelper.removeParam(device.address)
Source: ReConnectHelper.kt
Android:地址匹配辅助
isMatchAddress 供业务层判断「扫描到的设备是否属于某回连任务」,兼容升级前后的地址变化:
fun isMatchAddress(srcAddress: String, checkAddress: String): Boolean {
val param = getCacheParam(srcAddress)
return if (null == param || !BluetoothAdapter.checkBluetoothAddress(checkAddress)) false else checkAddress == param.deviceAddress || checkAddress == param.connectAddress
}
Source: ReConnectHelper.kt
iOS:配置回连选项
JLOtaReConnectOption *option = [JLOtaReConnectOption defaultOption];
option.deviceAuthorize = YES; // 需要设备认证
option.authKey = handshakeKeyData; // 设置握手秘钥
option.isWriteWithResponse = YES; // 写特征等待设备响应
// 自定义 UUID 时覆盖默认 AE00/AE01/AE02
Source: JLOtaReConnectOption.h
Flutter:切换自定义回连方式
// 查询当前是否使用自定义回连
final bool custom = await BleMethod.isUseCustomReConnectWay();
// 需要自行处理回连时关闭 SDK 内置回连
await BleMethod.setUseCustomReConnectWay(true);
Source: ble_method.dart
Configuration Options
回连机制的配置分散在三个层面:iOS 选项对象、Android 回连参数、以及 Flutter 侧的开关。
iOS:JLOtaReConnectOption 属性
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
deviceAuthorize | BOOL | NO | 回连时是否需要设备认证(配对) |
authKey | NSData *(可空) | nil | 握手/配对秘钥,deviceAuthorize = YES 时必填 |
serviceUUID | NSString * | @"AE00" | 设备 GATT 服务 UUID |
writeUUID | NSString * | @"AE01" | 写特征 UUID |
readUUID | NSString * | @"AE02" | 读特征 UUID |
isWriteWithResponse | BOOL | NO | 写特征是否需要设备响应 |
Android:ReconnectParam
| 参数 | 类型 | 说明 |
|---|---|---|
deviceAddress | String | 目标设备地址(升级前地址,用于匹配) |
isUseNewADV | Boolean | 是否使用新 ADV 方式(设备升级后地址漂移时置 true) |
connectAddress | String? | 实际连接到的地址,回连过程中由 SDK 写入 |
Android:内部调度常量
| 常量 | 值 | 说明 |
|---|---|---|
RECONNECT_TIMEOUT | DeviceReConnectManager.RECONNECT_TIMEOUT(约 65 秒) | 单次回连总超时,超时后停止扫描并清空任务 |
SCAN_TIMEOUT | SDK 内部定义 | 单次 LE 扫描窗口 |
FAILED_DELAY | SDK 内部定义 | 扫描冲突/失败后的重试延迟 |
Flutter:方法通道参数
| 常量 | 值 | 说明 |
|---|---|---|
METHOD_IS_USE_CUSTOM_RECONNECT_WAY | 通道方法名 | 查询是否使用自定义回连 |
METHOD_SET_USE_CUSTOM_RECONNECT_WAY | 通道方法名 | 设置是否使用自定义回连 |
ARG_IS_CUSTOM | bool 参数 | 自定义回连开关值 |
API Reference
Flutter(Dart)
BleMethod.isUseCustomReConnectWay() → Future<bool>
查询当前是否启用「自定义回连方式」。
- 返回:
true表示上层自行处理回连;false表示使用 SDK 内置回连。原生返回 null 时兜底为false。 - 抛出:
PlatformException(原生通道调用失败时,先打印日志再 rethrow)。
BleMethod.setUseCustomReConnectWay(bool isCustom) → Future<void>
设置是否启用自定义回连方式。
- 参数:
isCustom——true关闭 SDK 内置回连,false启用 SDK 内置回连。 - 抛出:
PlatformException(原生通道调用失败时,先打印日志再 rethrow)。
Android(Kotlin)
ReConnectHelper.putParam(param: ReconnectParam?): Boolean
注册一个回连任务。
- 参数:
param—— 目标地址与新 ADV 标志;null 直接返回 false。 - 返回:
true表示已加入(或已存在相同任务);false表示入队失败。 - 副作用:首次入队时启动总超时并立即调度
MSG_PROCESS_TASK。
ReConnectHelper.removeParam(address: String)
移除指定地址对应的回连任务;任务列表清空时同时移除总超时消息,否则继续调度下一任务。
ReConnectHelper.isMatchAddress(srcAddress: String, checkAddress: String): Boolean
判断 checkAddress 是否命中 srcAddress 对应的回连任务(匹配 deviceAddress 或已记录的 connectAddress)。
ReConnectHelper.isReconnecting: Boolean
查询当前是否有回连任务正在进行(依据 MSG_RECONNECT_TIMEOUT 消息是否存在)。
ReConnectHelper.release()
释放资源:清空参数与广播缓存、移除所有消息并注销 BleEventCallback。
iOS(Objective-C)
+ (JLOtaReConnectOption *)defaultOption
返回带默认值(serviceUUID = AE00、writeUUID = AE01、readUUID = AE02、isWriteWithResponse = NO)的回连选项实例。
Failure Modes, Edge Cases & Concurrency
回连超时
总超时 RECONNECT_TIMEOUT(约 65 秒)触发后,MSG_RECONNECT_TIMEOUT 处理器会停止扫描并清空全部待回连参数。这意味着长时间找不到设备时回连会静默终止,上层需依赖 OTAReconnect 之外的状态(如连接失败回调)感知失败并决定是否重试。若设备恢复广播较慢,需要上层在超时后重新 putParam。
蓝牙关闭与重新打开
onAdapterChange 回调中,若蓝牙被关闭则任务保持挂起(isReconnecting 仍为 true);蓝牙重新打开后立即发送 MSG_PROCESS_TASK 恢复回连。注意:若关闭期间总超时消息到期,任务仍会被清空,因此蓝牙关闭时长不能超过超时窗口。
扫描冲突
processReconnectTask 与 onDiscoveryBleChange 都做了「正在扫描则不重复启动」的保护,冲突时通过 FAILED_DELAY 延迟重试,避免 startLeScan 并发调用。所有调度均经主线程 Handler 串行化,mParams 与 mBleAdvCache 无锁访问也不会产生并发竞争。
地址漂移(新 ADV 方式)
isUseNewADV = true 时,设备升级后广播地址变化,匹配依赖 onDiscoveryBle 中解析出的 oldBleAddress 与 mBleAdvCache 缓存。若 OTA 广播未能解析(advMsg == null),isReconnectDevice 会退回按 device.address 比对,可能无法匹配新地址——因此新 ADV 方式强依赖广播数据完整性。
重复入队与幂等
ReconnectParam.equals/hashCode 基于 deviceAddress + isUseNewADV,重复 putParam 同一设备不会产生重复任务;removeParam 对不存在的地址安全返回,整个机制具备幂等性。
iOS 配置缺失
deviceAuthorize = YES 但未设置 authKey 时,握手阶段可能失败导致回连中断;isWriteWithResponse 与设备固件能力不匹配时可能出现写入超时。SDK 未在头文件中声明这些校验逻辑,业务方需保证配置与设备固件一致。
Performance & Operational Notes
- 扫描功耗控制:回连采用「限定窗口扫描(
SCAN_TIMEOUT)+ 总超时(65 秒)」两级节流,避免无限扫描耗尽电量;蓝牙适配器未开启时任务挂起而非轮询。 - 主线程调度成本:所有状态变更与回调分发都收敛到主线程
Handler,单设备回连场景开销可忽略;但mParams遍历(isReconnectDevice/getCacheParam)为 O(n),若同一时刻注册大量回连任务(不常见),匹配耗时会线性增长。 - 缓存内存:
mBleAdvCache在扫描期间持续缓存命中 OTA 标识的广播,仅在release()时统一清理;长扫描窗口下大量设备广播可能占用内存,但对单设备 OTA 场景影响极小。 - 跨端一致性:Flutter 层开关(自定义回连)与原生内置回连互斥,切换后需保证业务侧与 SDK 侧对「谁负责回连」的认知一致,否则可能出现双重回连或无人回连。
Extension Points
- 自定义回连方式:
setUseCustomReConnectWay(true)后上层可完全接管回连(自行扫描、配对、连接),SDK 内置逻辑让位。这是 Flutter 层最主要的扩展入口。 - 新 ADV 模式:
ReconnectParam.isUseNewADV允许适配「升级后更换广播地址」的设备族;新增设备协议时只需保证广播可被parseOTAFlagFilterWithBroad解析出oldBleAddress。 - iOS UUID 定制:
JLOtaReConnectOption的serviceUUID/writeUUID/readUUID支持私有 GATT 服务,厂商可通过覆盖默认AE00/AE01/AE02接入自定义协议。 - 认证回连:
deviceAuthorize+authKey为安全要求较高的场景(如防蹭连)预留配对握手扩展。 - 状态上报扩展:
OTAReconnect继承自OTAState状态体系,新增回连相关状态时可在同一状态族内扩展,保持 OTA 流程对外回调结构统一。
Related Links
- ble_method.dart(Flutter 桥接接口)
- ReConnectHelper.kt(Android 回连任务调度器)
- OTAReconnect.kt(OTA 回连状态模型)
- JLOtaReConnectOption.h(iOS 回连选项)
- 相关目录页:OTA 升级主流程(固件推送与状态机)、蓝牙连接管理(BleManager 扫描/连接底层)