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

    • 项目概述与能力总览
    • 快速开始与 SDK 集成
  • SDK 核心接口

    • 发送接口 BleMethod
    • 接收接口 BleEventStream
    • 数据模型与常量定义
  • 平台原生实现

    • Android 原生层
    • iOS 原生层架构
    • iOS 蓝牙管理与 SDK 运行
    • 辅助连接与广播音箱
  • OTA 升级功能

    • 升级流程与传输通道
    • 自动回连机制
    • 复用空间升级
    • 自定义命令
  • 示例应用

    • 页面结构与用户旅程
    • 设备扫描与连接管理
    • 固件文件管理
    • 升级执行与状态展示
    • 设置与调试
  • 文档与支持

    • 接口文档与收发说明
    • 调试与问题排查

自动回连机制

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 只按升级前的地址连接,升级过程中一旦断链就无法再次找到设备。自动回连机制正是为此设计:

  1. 地址漂移适配:通过 OTA 广播中的 oldBleAddress 将新地址映射回旧地址,确保升级前后的设备被识别为同一目标;
  2. 断链自愈:连接断开后自动重新扫描并回连,无需用户手动操作;
  3. 超时保护:回连任务有总超时(约 65 秒),避免无限期扫描消耗电量;
  4. 自定义回连开关:允许上层(Flutter)选择使用 SDK 内置回连还是自行处理回连。

关键概念

概念说明
ReconnectParam一次回连任务的参数:目标设备地址 + 是否使用新 ADV 方式
mBleAdvCache扫描到的 OTA 广播缓存,用于新地址 → 旧地址映射
MSG_RECONNECT_TIMEOUT回连总超时消息(65 秒),触发后停止扫描并清空任务
MSG_PROCESS_TASK处理回连任务的消息,驱动扫描-连接状态机
JLOtaReConnectOptioniOS 端回连配置:服务/读写特征 UUID、认证、写响应方式
isUseCustomReConnectWayFlutter 侧查询/设置是否使用自定义回连方式的通道方法

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

各阶段说明:

  1. 入队(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

  1. 任务处理(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

  1. 设备识别(isReconnectDevice + onDiscoveryBle):扫描到广播后先用 ParseDataUtil.parseOTAFlagFilterWithBroad 解析 OTA 标识,命中则缓存广播。识别规则分两种模式:
    • 新 ADV 模式(isUseNewADV == true):设备广播地址已改变,需要把广播中的 oldBleAddress 与任务的目标地址比对;
    • 旧地址模式:直接比对设备地址。 命中后停止扫描、记录实际连接地址 connectAddress 并发起连接。
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

  1. 结果处理(onBleConnection):连接成功(STATE_CONNECTED)即视为回连完成,调用 removeParam 清理任务并移除对应超时消息;若连接断开(STATE_DISCONNECTED)则重新发送 MSG_PROCESS_TASK 继续回连。

  2. 超时兜底: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 属性

属性类型默认值说明
deviceAuthorizeBOOLNO回连时是否需要设备认证(配对)
authKeyNSData *(可空)nil握手/配对秘钥,deviceAuthorize = YES 时必填
serviceUUIDNSString *@"AE00"设备 GATT 服务 UUID
writeUUIDNSString *@"AE01"写特征 UUID
readUUIDNSString *@"AE02"读特征 UUID
isWriteWithResponseBOOLNO写特征是否需要设备响应

Android:ReconnectParam

参数类型说明
deviceAddressString目标设备地址(升级前地址,用于匹配)
isUseNewADVBoolean是否使用新 ADV 方式(设备升级后地址漂移时置 true)
connectAddressString?实际连接到的地址,回连过程中由 SDK 写入

Android:内部调度常量

常量值说明
RECONNECT_TIMEOUTDeviceReConnectManager.RECONNECT_TIMEOUT(约 65 秒)单次回连总超时,超时后停止扫描并清空任务
SCAN_TIMEOUTSDK 内部定义单次 LE 扫描窗口
FAILED_DELAYSDK 内部定义扫描冲突/失败后的重试延迟

Flutter:方法通道参数

常量值说明
METHOD_IS_USE_CUSTOM_RECONNECT_WAY通道方法名查询是否使用自定义回连
METHOD_SET_USE_CUSTOM_RECONNECT_WAY通道方法名设置是否使用自定义回连
ARG_IS_CUSTOMbool 参数自定义回连开关值

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

  1. 自定义回连方式:setUseCustomReConnectWay(true) 后上层可完全接管回连(自行扫描、配对、连接),SDK 内置逻辑让位。这是 Flutter 层最主要的扩展入口。
  2. 新 ADV 模式:ReconnectParam.isUseNewADV 允许适配「升级后更换广播地址」的设备族;新增设备协议时只需保证广播可被 parseOTAFlagFilterWithBroad 解析出 oldBleAddress。
  3. iOS UUID 定制:JLOtaReConnectOption 的 serviceUUID / writeUUID / readUUID 支持私有 GATT 服务,厂商可通过覆盖默认 AE00/AE01/AE02 接入自定义协议。
  4. 认证回连:deviceAuthorize + authKey 为安全要求较高的场景(如防蹭连)预留配对握手扩展。
  5. 状态上报扩展:OTAReconnect 继承自 OTAState 状态体系,新增回连相关状态时可在同一状态族内扩展,保持 OTA 流程对外回调结构统一。

Related Links

  • ble_method.dart(Flutter 桥接接口)
  • ReConnectHelper.kt(Android 回连任务调度器)
  • OTAReconnect.kt(OTA 回连状态模型)
  • JLOtaReConnectOption.h(iOS 回连选项)
  • 相关目录页:OTA 升级主流程(固件推送与状态机)、蓝牙连接管理(BleManager 扫描/连接底层)
Prev
升级流程与传输通道
Next
复用空间升级