杰理 SDK 文档中心
首页
首页
  • 项目概览

    • 项目简介与核心能力
    • 快速开始
    • 工程结构与依赖库
  • 核心功能

    • RCSP OTA 升级流程
    • BLE 升级通道
    • SPP 升级通道
    • 自动回连机制
  • 蓝牙通信架构

    • 蓝牙抽象层与基础组件
    • BLE 模块实现
    • SPP 模块实现
    • 蓝牙管理与 OTA 管理器
  • 示例应用

    • 应用入口与启动流程
    • 主界面与设备连接交互
    • 关于、日志与辅助页面
  • 调试与运维

    • 日志系统与调试技巧
    • 问题排查与技术支持
  • 开发者指南

    • SDK 版本历史
    • 集成与二次开发指南

BLE 模块实现

BLE(Bluetooth Low Energy)模块是 HarmonyOS-JL_OTA 蓝牙架构中基于 OpenHarmony @kit.ConnectivityKit 封装的低功耗蓝牙通信能力,负责设备扫描、GATT 连接、MTU 协商、服务发现、特征值订阅与数据发送,为上层 OTA 升级提供统一、事件驱动的 BLE 传输通道。

Purpose and Scope

本文档深入剖析 BLE 模块的完整实现,覆盖:

  • BleImpl 的核心职责与内部机制(扫描、连接、发数、事件分发);
  • BleDevice 设备模型与 BluetoothManager 门面(Facade)层的分发逻辑;
  • 基于 ble / access / connection / hid 系统 API 的真实控制流;
  • 扫描去重、MTU 协商、服务发现、系统已连接设备探测等关键算法;
  • 事件订阅机制、配置项、失败模式与扩展点。

本页边界:蓝牙架构中的经典蓝牙(EDR/SPP)通道由 spp 包(SppImpl、SppDevice)实现,属于同级的独立能力,仅在 BluetoothManager 分发逻辑中作为对照提及;RCSP 协议封装(lib_rcsp)与 OTA 升级流程不在本页范围。如需了解 SPP 通道,参见「SPP 模块实现」;如需了解上层 OTA 编排,参见「BluetoothOTAManager / OTA 升级流程」。

Overview

在 OTA 升级场景中,BLE 是双模设备最常用的传输通道:设备体积小、功耗低,适合通过 GATT 服务承载 RCSP 协议报文。本模块的核心设计目标有三:

  1. 隔离系统 API 差异:将 OpenHarmony 的 ble、access、connection、hid 等系统能力封装为一组面向业务的接口(IBleScan、IBleConnect),上层无需感知系统 API 细节。
  2. 事件驱动与解耦:通过 callbacksMap 实现发布/订阅模式,扫描状态、设备发现、连接状态、MTU 变化、特征值变化均以回调方式对外广播,业务层只需 on/off 订阅。
  3. 设备类型多态分发:BluetoothManager 作为门面,依据设备实例类型(BleDevice vs SppDevice)或通讯方式("BLE" vs "EDR")自动路由到对应实现,调用方可以写出与传输通道无关的代码。

模块整体位于 entry/src/main/ets/bluetooth/ 目录下,其中 ble/ 子目录为 BLE 专属实现,base/ 子目录存放 BLE/SPP 共享的基础类型与工具。

Architecture

flowchart TD
    subgraph sg_Business["业务层 (OTA / 页面)"]
        OTA["BluetoothOTAManager"]
        Page["设备扫描/连接页面"]
    end

    subgraph sg_Facade["门面层"]
        Manager["BluetoothManager"]
    end

    subgraph sg_Ble["BLE 模块 (bluetooth/ble)"]
        BleImpl["BleImpl (implements IBleScan, IBleConnect)"]
        BleDevice["BleDevice (extends BluetoothDevice)"]
        SendHandler["BleSendDataHandler"]
        ScanCfg["BleScanSettingConfigure"]
        ConnCfg["BleConnectSettingConfigure"]
    end

    subgraph sg_Base["共享基础层 (bluetooth/base)"]
        BluetoothDevice["BluetoothDevice"]
        IConnect["IConnect (ConnectState / ConnectStateInfo)"]
        IScan["IScan (ScanState / ScanStateInfo)"]
        ErrConst["BluetoothErrorConstant"]
        BufferQueue["BufferQueue"]
    end

    subgraph sg_System["OpenHarmony 系统能力 (@kit.ConnectivityKit)"]
        BleKit["ble (startBLEScan / GattClientDevice)"]
        AccessKit["access (蓝牙开关状态)"]
        ConnKit["connection (设备名查询)"]
        HidKit["hid (HidHostProfile 已连接设备)"]
    end

    OTA --> Manager
    Page --> Manager
    Manager -->|"instanceof / communicationWay 路由"| BleImpl
    Manager -->|"SPP 路由"| SPP["SppImpl (spp 包,另页介绍)"]

    BleImpl --> BleDevice
    BleImpl --> ScanCfg
    BleImpl --> ConnCfg
    BleImpl --> SendHandler
    BleDevice --> BluetoothDevice
    BleImpl --> IConnect
    BleImpl --> IScan
    BleImpl --> ErrConst
    SendHandler --> BufferQueue

    BleImpl -->|"ble 系统 API"| BleKit
    BleImpl -->|"access.on 状态监听"| AccessKit
    BleImpl -->|"getRemoteDeviceName"| ConnKit
    BleImpl -->|"getConnectedDevices"| HidKit

架构说明:

  • BluetoothManager 是唯一的对外入口(门面),持有 _bleImpl 与 _sppImpl 两个实现实例,通过 instanceof 判断设备类型或通过 communicationWay 属性(默认 "BLE")路由调用。相关代码见 BluetoothManager.ets。
  • BleImpl 同时实现 IBleScan 与 IBleConnect 两个接口,是 BLE 模块的核心,内部持有扫描配置、连接配置、设备列表、回调映射表等状态,见 BleImpl.ets。
  • BleDevice 继承 BluetoothDevice,在基类之上补充 scanResult、mtu、gattClientDevice、notifyCharacteristics、writeCharacteristics 等 BLE 专属字段,见 BleDevice.ets。
  • base/ 包提供两个通道共享的抽象:BluetoothDevice(设备基类)、IConnect/IScan(事件类型与状态结构体)、BluetoothErrorConstant(统一错误码)、BufferQueue 与 BaseSendDataHandler(发数缓冲队列基类)。

模块组成与文件职责

文件职责
ble/BleImpl.etsBLE 核心实现:扫描、连接、MTU、服务发现、发数入口、事件分发
ble/BleDevice.etsBLE 设备模型,扩展 BluetoothDevice
ble/BleScanSettingConfigure.ets扫描参数配置(超时、是否包含系统已连接设备等)
ble/BleConnectSettingConfigure.ets连接参数配置(目标 MTU 等)
ble/BleSendDataHandler.etsBLE 发数处理器(继承 BaseSendDataHandler,含分包/队列)
ble/IBleScan.etsBLE 扫描接口与事件类型(ScanEventType、ScanState)
ble/IBleConnect.etsBLE 连接接口与事件类型(BleConnectEventType、MTUInfo、BLECharacteristicInfo)
base/BluetoothDevice.ets设备基类与 BluetoothDeviceType("BLE" / "EDR")
base/IConnect.ets连接事件常量(BaseConnectEventTypeConstant)、ConnectState、ConnectStateInfo
base/IScan.ets扫描事件常量(ScanEventType)、ScanState、ScanStateInfo
base/BluetoothErrorConstant.ets蓝牙错误码常量(如 ERROR_IS_CONNECTING)
base/BufferQueue.ets发送缓冲队列(供发数处理器使用)
base/BaseSendDataHandler.ets发数处理器基类
BluetoothManager.ets门面:按设备类型/通讯方式路由到 BleImpl 或 SppImpl

事件订阅机制:callbacksMap

BleImpl 用一张 Map<string, Array<Callback<never>>> 管理全部订阅回调,这是整个模块的"消息总线"。业务层通过重载的 on/off 方法订阅五类事件:

事件类型回调载荷语义
ScanEventType.SCAN_STATE_CHANGEScanStateInfo扫描开始 / 失败 / 结束
ScanEventType.SCAN_DEVICE_FINDBleDevice[]发现设备(全量列表)
BaseConnectEventTypeConstant.CONNECT_STATE_CHANGEConnectStateInfo<BleDevice>连接成功 / 失败
BleConnectEventTypeConstant.CONNECT_MTU_CHANGEMTUInfoMTU 协商结果
BleConnectEventTypeConstant.CONNECT_BLE_CHARACTERISTIC_CHANGEBLECharacteristicInfo远端特征值通知
on(type: ScanEventType | BleConnectEventType,
  callback: Callback<ScanStateInfo, void> | Callback<BleDevice[], void> | Callback<MTUInfo, void> | Callback<ConnectStateInfo<BleDevice>, void>
    | Callback<BLECharacteristicInfo, void>
): void {
  let typeCallbackArray = this.callbacksMap.get(type)
  if (typeCallbackArray == undefined) {
    typeCallbackArray = new Array()
  }
  typeCallbackArray.push(callback)
  this.callbacksMap.set(type, typeCallbackArray)
}

Source: BleImpl.ets

off 的行为刻意做了区分:传 callback 时从数组中移除该回调;不传时清空该事件类型下的全部回调。这种"全清/单删"二态设计让业务层既可以精确退订,也可以在页面销毁时一键清理。模块内部每个事件(如 _onScanStart、_onFound、onConnectStateChanged)都会从 callbacksMap 取出回调数组并逐个调用,实现"内部系统回调 → 业务回调"的桥接。

扫描实现

扫描状态与配置

扫描相关的私有状态包括:

  • mIsScanning: boolean — 是否正在扫描;
  • mScanSettingConfigure: BleScanSettingConfigure — 扫描参数(scanTimeOut、isContainSystemsConnectedDevice 等);
  • mScanDevList: Array<BleDevice> — 已发现设备列表(去重后);
  • mScanTimeoutID?: number — 扫描超时定时器;
  • mScanSystemConnectedDevInterval?: number — 系统已连接设备轮询定时器。

对外接口 startScan(scanTimeOut?) / refreshScan() / stopScan() / isScanning() / getScanSettingConfigure() / setScanSettingConfigure() 构成完整的扫描生命周期。

startScan(scanTimeOut?: number | undefined): void {
  if (scanTimeOut != undefined) { //更新扫描时间
    this.mScanSettingConfigure.scanTimeOut = scanTimeOut
  }
  if (this.mIsScanning) { //正在扫描中
    this.refreshScan()
  } else {
    this.mScanDevList = new Array()
    this._stopTiming()
    this._startTiming()
    this._startScan()
  }
}

Source: BleImpl.ets

设计意图:startScan 是幂等友好的——重复调用不会叠加扫描,而是等价于"清空设备列表并重新计时"(refreshScan),避免上层因多次点击产生多个并发的系统扫描会话。

底层扫描启动

private _startScan() {
  Log.d(TAG, "_startScan")
  try {
    try {
      this._onBluetoothDeviceFound();
    } catch (e) {
      Log.d(TAG, "catch _onBluetoothDeviceFound")
    }
    let scanOptions: ble.ScanOptions = {
      interval: 500,
      dutyMode: ble.ScanDuty.SCAN_MODE_BALANCED,
      matchMode: ble.MatchMode.MATCH_MODE_AGGRESSIVE,
    };
    ble.startBLEScan(null, scanOptions);
    this.mIsScanning = true
    this._onScanStart()
    if (this.mScanSettingConfigure.isContainSystemsConnectedDevice) {
      this._startSystemConnectedDeviceFound()
    }
  } catch (err) {
    Log.e(TAG, `ble scan error:${JSON.stringify(err)} `);
    Log.e(TAG, `ble scan error:${(err as BusinessError).code} message:${(err as BusinessError).message}`);
    this._stopTiming()
    this._onScanFailed(err)
  }
}

Source: BleImpl.ets

要点:

  • 系统扫描参数固定为 interval: 500、SCAN_MODE_BALANCED(均衡功耗与发现速度)、MATCH_MODE_AGGRESSIVE(激进匹配,提高发现成功率),这是对 OTA 场景"尽快找到设备"需求的折中。
  • 先注册 BLEDeviceFind 监听再调用 startBLEScan,避免漏掉启动瞬间的回调。
  • 任何异常都会走 _stopTiming() + _onScanFailed(err),保证超时定时器不会残留,同时把 BusinessError 透传给订阅者。

设备去重与增量上报

_handlerFoundDevice 是扫描结果的核心算法:以 deviceId 为 key 去重,仅当 RSSI 或广播数据(scanResult.data)变化时才更新列表,最终以"整表替换"方式触发 _onFound。这避免了高频 BLEDeviceFind 回调导致的 UI 抖动,同时保证业务层拿到的永远是当前最新列表。

private _handlerFoundDevice(scanDevs: BleDevice[]) {
  let isChange = false
  for (let i = 0; i < scanDevs.length; i++) {
    const scanDevice = scanDevs[i];
    let isContain = false
    for (let y = 0; y < this.mScanDevList.length; y++) {
      const element = this.mScanDevList[y];
      if (scanDevice.deviceId === element.deviceId) {
        isContain = true
        if (scanDevice.scanResult?.rssi !== element.scanResult?.rssi) { //rssi改变
          this.mScanDevList[y] = scanDevice
          isChange = true
        } else if (
          u8ToHexStr(scanDevice.scanResult != undefined ? new Uint8Array(scanDevice.scanResult.data) :
            new Uint8Array()) !==
          u8ToHexStr(element.scanResult != undefined ? new Uint8Array(element.scanResult.data) :
            new Uint8Array())) { //广播包改变
          this.mScanDevList[y] = scanDevice
          isChange = true
        }
        break
      }
    }
    if (!isContain) {
      isChange = true
      this.mScanDevList.push(scanDevice)
    }
  }
  if (isChange) {
    this._onFound(this.mScanDevList)
  }
}

Source: BleImpl.ets

系统已连接设备探测

普通 BLE 扫描无法发现"已与系统配对/连接但不在广播"的设备。为此 BleImpl 在扫描启动时(若配置 isContainSystemsConnectedDevice 为 true)额外启动一个 500ms 的 setInterval,通过 hidProfile.getConnectedDevices() 轮询 HID 已连接设备,再经 connection.getRemoteDeviceName() 补全设备名,标记 isSystemConnected = true 后合并进设备列表。源码注释还记录了踩坑经验:系统 ble.getConnectedBLEDevices() 接口"好像没有效果",故改用 hidProfile 方案(见 BleImpl.ets)。

扫描超时控制

_startTiming() 用 setTimeout(..., mScanSettingConfigure.scanTimeOut) 建立扫描超时,超时后先清理系统设备轮询定时器再调用 _stopScan();_stopTiming() 负责 clearTimeout。_stopScan() 内部调用 ble.stopBLEScan() 并 ble.off("BLEDeviceFind", ...) 注销监听,随后广播 SCAN_STATE_FINISH。整套时序保证:无论主动停止还是超时停止,系统资源都会被正确释放。

连接实现

连接状态管理

连接部分维护两个设备数组:_connectingDeviceArray(连接中)与 _connectedDeviceArray(已连接),外加一份 _connectConfigure: BleConnectSettingConfigure(目标 MTU 等)。connect 入口首先做状态短路:

connect(device: BleDevice, success?: Callback<BleDevice, void> | undefined,
  fail?: Callback<BusinessError<void>, void> | undefined): void {
  if (this.isConnected(device)) { //已连接
    success?.(device)
    return;
  }
  if (this.isConnecting(device)) {
    const error: BusinessError = {
      code: BluetoothErrorConstant.ERROR_IS_CONNECTING,
      name: TAG,
      message: 'is connecting'
    }
    fail?.(error)
    return
  }
  try {
    const gattClientDevice = ble.createGattClientDevice(device.deviceId)
    device.gattClientDevice = gattClientDevice
    this._connectingDeviceArray.push(device)
    // ...(连接状态回调、MTU 协商、服务初始化)

Source: BleImpl.ets

设计意图:连接流程采用"异步回调链",依次为 GATT 连接 → MTU 协商 → 服务发现 → 特征值订阅 → 上报成功,每一步都必须在回调中确认上一步完成后才继续,符合 BLE 协议严格的时序约束。

连接核心流程

sequenceDiagram
    participant Biz as 业务层
    participant M as BluetoothManager
    participant Impl as BleImpl
    participant Gatt as GattClientDevice
    participant Sys as ble 系统能力

    Biz->>M: connect(bleDevice)
    M->>Impl: bleImpl.connect(device, success, fail)
    Impl->>Impl: isConnected/isConnecting 短路检查
    Impl->>Sys: ble.createGattClientDevice(deviceId)
    Impl->>Gatt: gattClientDevice.connect()
    Gatt-->>Impl: onConnectStateChange (STATE_CONNECTED)
    Impl->>Gatt: setBLEMtuSize(_connectConfigure.mtu)
    Gatt-->>Impl: onMtuChange (mtu)
    Impl->>Impl: device.mtu = mtu
    Impl->>Impl: _getBLEDeviceServices(device)
    Impl->>Gatt: getServices / 订阅 BLECharacteristicChange
    Gatt-->>Impl: 服务发现完成
    Impl-->>Biz: success(device) + CONNECT_STATE_SUCCESS

流程要点:

  1. 状态短路:已连接直接回调 success;连接中则回调 fail 并携带 ERROR_IS_CONNECTING,防止重复建链。
  2. 创建 GATT 客户端:ble.createGattClientDevice(device.deviceId) 生成系统级连接对象并挂到 device.gattClientDevice,随后推入 _connectingDeviceArray。
  3. MTU 协商:onConnectStateChange 收到 STATE_CONNECTED 后调用 setBLEMtuSize(that._connectConfigure.mtu);onMtuChange 回调里用 setMtuTimeout 定时器做超时保护,仅当定时器尚未触发时把 device.mtu = mtu 并进入服务初始化(见 BleImpl.ets)。
  4. 服务初始化与成功上报:initBLEDeviceServices 调用 _getBLEDeviceServices(dev) 拉取服务列表,成功后注册 BLECharacteristicChange 监听并回调 success + CONNECT_STATE_SUCCESS;失败则 disconnect(dev)、回调 fail 并广播 CONNECT_STATE_FAILED(见 BleImpl.ets)。

蓝牙开关状态监听

BleImpl 构造函数即注册 access.on('stateChange') 全局监听:

  • STATE_OFF:置 isAccessOn = false,遍历 _connectedDeviceArray 逐个 disconnect,保证蓝牙关闭时上层状态立即收敛;
  • STATE_ON:置 isAccessOn = true,把上次关闭时未断开成功的设备(记录在 waitDisconnectDevices)重新执行 disconnect + close 清理,然后清空等待队列。

见 BleImpl.ets。这是模块在系统级异常下的自愈机制:系统蓝牙被用户关闭再打开后,不会留下悬挂的 GATT 连接。

数据发送

sendData 是 OTA 写特征值的统一入口。它先在已连接设备中查找目标设备,再从 writeCharacteristics 中匹配 serviceId + characteristicId,最终委托给 BleSendDataHandler(继承自 base/BaseSendDataHandler,配合 BufferQueue 完成分包与顺序发送):

sendData(bluetoothDevice: BleDevice, serviceId: string, characteristicId: string, data: Uint8Array) {
  const devInfo = this.getConnectedDevInfo(bluetoothDevice)
  if (devInfo) {
    const characteristic = devInfo.writeCharacteristics.find(item => item.serviceUuid === serviceId &&
      item.characteristicUuid === characteristicId)
    if (characteristic) {
      // Log.i(TAG, "sendData :" + u8ToHexStr(new Uint8Array(data)))
      this.bleSendDataHandler.sendData(devInfo, characteristic, data)
    } else {
      Log.e(TAG, "sendData device is not find write characteristic")
    }
  } else {
    Log.e(TAG, "sendData device is not connected")
  }
}

Source: BleImpl.ets

设计意图:发送前强制校验"设备已连接 + 特征值存在"两个前置条件,避免向未连接的 GATT 对象写入导致系统异常。特征值列表(writeCharacteristics/notifyCharacteristics)在服务发现阶段填充,发送时按 UUID 精确定位,天然支持多服务多特征值的设备。

门面分发:BluetoothManager

BluetoothManager 是 BLE 与 SPP 的统一入口。其 connect/disconnect/isConnecting/isConnected 等方法按设备实例类型路由,getConnectedDevice/isScanning/startScan 等按 communicationWay(默认 "BLE")路由:

connect(device: BluetoothDevice, success?: Callback<BluetoothDevice>,
  fail?: Callback<BusinessError>) {
  if (device instanceof SppDevice) { //根据设备类型判断-Spp
    this.sppImpl.connect(device, success, fail)
  } else if (device instanceof BleDevice) { //根据设备类型判断-Ble
    this.bleImpl.connect(device, success, fail)
  } else { //无法判断设备类型
  }
}

Source: BluetoothManager.ets

这种"类型即路由"的设计让上层只需持有 BluetoothDevice 抽象即可操作任意通道;新增通道只需扩展 BluetoothDevice 子类并在门面中增加分支。communicationWay 属性用于扫描类操作在未显式传类型时的默认通道选择(构造函数中默认 "BLE"),见 BluetoothManager.ets。

设备模型与使用示例

BleDevice 设备模型

export class BleDevice extends BluetoothDevice {
  /**扫描结果上报数据*/
  scanResult?: ble.ScanResult;
  mtu: number = 23
  gattClientDevice?: ble.GattClientDevice
  notifyCharacteristics = new Array<ble.BLECharacteristic>()
  writeCharacteristics = new Array<ble.BLECharacteristic>()

  constructor(deviceId: string, scanResult?: ble.ScanResult) {
    super(deviceId);
    this.type = "BLE"
    if (scanResult != undefined) {
      this.scanResult = scanResult
      this.deviceName = scanResult.deviceName
      this.connectable = scanResult.connectable
      this.rssi = scanResult.rssi
    }
  }
}

Source: BleDevice.ets

设计意图:mtu 默认 23(BLE 4.x 最小 MTU),实际值在连接后由 onMtuChange 覆盖;notifyCharacteristics/writeCharacteristics 在服务发现阶段填充,是 sendData 与特征值订阅的数据基础。

典型使用流程(基于源码推导的组合)

以下组合展示业务层如何串联扫描 → 订阅 → 连接 → 发数(调用均来自 BleImpl 的公开接口,签名见 API 参考):

// 1. 通过门面获取 BLE 实现
const manager = new BluetoothManager()
const bleImpl = manager.bleImpl

// 2. 订阅扫描与连接事件
bleImpl.on(ScanEventType.SCAN_DEVICE_FIND, (devices: BleDevice[]) => {
  // 刷新设备列表(全量)
})
bleImpl.on(ScanEventType.SCAN_STATE_CHANGE, (info: ScanStateInfo) => {
  // SCAN_STATE_START / SCAN_STATE_FINISH / SCAN_STATE_FAILED
})
bleImpl.on(BaseConnectEventTypeConstant.CONNECT_STATE_CHANGE,
  (info: ConnectStateInfo<BleDevice>) => {
    // CONNECT_STATE_SUCCESS / CONNECT_STATE_FAILED
  })

// 3. 开始扫描(10 秒超时)
bleImpl.startScan(10 * 1000)

// 4. 对目标设备发起连接
bleImpl.connect(device, (dev) => {
  // 连接成功,可开始 sendData
  bleImpl.sendData(dev, serviceId, characteristicId, new Uint8Array([...]))
}, (error) => {
  // 连接失败,error.code 可对照 BluetoothErrorConstant
})

// 5. 页面销毁时清理
bleImpl.off(ScanEventType.SCAN_DEVICE_FIND)
bleImpl.off(ScanEventType.SCAN_STATE_CHANGE)
bleImpl.off(BaseConnectEventTypeConstant.CONNECT_STATE_CHANGE)
bleImpl.stopScan()

注:上述调用序列是对 BleImpl 公开方法(on/off/startScan/stopScan/connect/sendData)的按序组合,方法签名与源码一致;具体业务编排请参考上层页面代码。

配置选项

配置类选项类型默认值说明
BleScanSettingConfigurescanTimeOutnumber由配置类定义(具体值见 BleScanSettingConfigure.ets)扫描超时(毫秒),startScan(scanTimeOut) 可临时覆盖
BleScanSettingConfigureisContainSystemsConnectedDeviceboolean由配置类定义是否在扫描中合并系统已连接(HID)设备
BleConnectSettingConfiguremtunumber由配置类定义(BleDevice.mtu 初始为 23)连接后请求的目标 MTU 大小,经 setBLEMtuSize 协商
BluetoothManagercommunicationWayBluetoothDeviceType"BLE"未显式指定类型时扫描/查询类操作的默认通道

说明:BleScanSettingConfigure 与 BleConnectSettingConfigure 的具体默认值位于各自文件内,本次文档未展开读取,使用前请以源码为准。

API 参考

BleImpl(实现 IBleScan, IBleConnect)

方法签名说明
onon(type, callback): void订阅事件(重载支持 5 类事件),重复订阅同一类型会追加回调
offoff(type, callback?): void退订;不传 callback 时清空该类型全部回调
isScanningisScanning(): boolean当前是否正在扫描
startScanstartScan(scanTimeOut?: number): void开始扫描;已在扫描则刷新列表与计时
refreshScanrefreshScan(): void清空设备列表并重新计时(仅扫描中有效)
stopScanstopScan(): void停止扫描并广播 SCAN_STATE_FINISH
getScanSettingConfiguregetScanSettingConfigure(): BleScanSettingConfigure获取扫描配置引用
setScanSettingConfiguresetScanSettingConfigure(cfg: BleScanSettingConfigure): void替换扫描配置
connectconnect(device: BleDevice, success?, fail?): void建立 GATT 连接(含 MTU/服务初始化)
disconnectdisconnect(device: BleDevice): void断开连接并清理状态
isConnectingisConnecting(device: BleDevice): boolean设备是否处于连接中
isConnectedisConnected(device: BleDevice): boolean设备是否已连接
getConnectedDevicegetConnectedDevice(): BleDevice[] | undefined获取已连接设备列表
sendDatasendData(device: BleDevice, serviceId: string, characteristicId: string, data: Uint8Array): void向指定特征值发送数据

失败回调参数:fail 回调携带 BusinessError,其中 code 取值来自 BluetoothErrorConstant(如 ERROR_IS_CONNECTING),message 为人类可读描述。

BluetoothManager(门面)

方法签名说明
connectconnect(device: BluetoothDevice, success?, fail?): void按 instanceof 路由到 bleImpl/sppImpl
disconnectdisconnect(device: BluetoothDevice): void按类型路由断开
getConnectedDevicegetConnectedDevice(type?): BleDevice[] | SppDevice[] | undefined按 communicationWay 或显式类型查询
isConnecting / isConnected(device): boolean按类型路由查询状态
isScanning / startScan(type?): boolean / (scanTimeOut?, type?): void按通道执行扫描操作
bleImpl / sppImplgetter直接访问底层实现实例

失败模式、边界情况与并发

已连接的重复连接(幂等短路)

connect 对已连接设备直接回调 success 而不重建 GATT 连接;对连接中的设备回调 fail(ERROR_IS_CONNECTING)。这避免了 OTA 流程中因重试或页面重复点击产生的"双连接"竞态。对应源码见 BleImpl.ets。

扫描失败与异常兜底

_startScan 整体包在 try/catch 中:系统扫描接口抛错时先 _stopTiming() 释放定时器,再 _onScanFailed(err) 广播 SCAN_STATE_FAILED 与原始 BusinessError,业务层可据此提示用户"蓝牙不可用/权限缺失"。_stopScan 中的 ble.stopBLEScan() 与 ble.off("BLEDeviceFind", ...) 同样有异常保护,避免重复停止时抛错。

MTU 协商超时保护

连接回调链中 setMtuTimeout 定时器保证:若 onMtuChange 迟迟不来,后续服务初始化不会被错误触发;一旦收到 MTU 回调则立即清除定时器。这是对"部分设备 MTU 协商不回调"这一真实兼容性问题的防御(见 BleImpl.ets)。

蓝牙关闭/重启的自愈

构造函数订阅的 access.on('stateChange') 在 STATE_OFF 时遍历断开全部已连接设备;STATE_ON 时清理 waitDisconnectDevices 中上次未断开的悬挂设备。这一机制防止系统蓝牙异常关闭后 GATT 对象泄漏,是模块级的状态收敛保障。

并发与线程模型

BleImpl 是单例式服务对象(由 BluetoothManager 构造一次),所有回调(BLEDeviceFind、BLEConnectionChangeState、BLECharacteristicChange、定时器)均由系统线程池回调到主线程执行队列,callbacksMap 的读写集中在这些回调路径上。扫描去重算法对 mScanDevList 的修改与 _onFound 的广播在同一调用栈内完成,未引入额外锁——这依赖 HarmonyOS 事件循环的单线程语义。上层若需多页面共享同一 BluetoothManager,需注意 communicationWay 与扫描状态是全局的。

已知边界

  • 系统 ble.getConnectedBLEDevices() 在目标平台无效果,实现改用 hidProfile.getConnectedDevices() 轮询,故"系统已连接设备"仅覆盖 HID 类型设备(源码注释已明确此限制)。
  • sendData 对未连接设备或未找到写特征值的情况仅记录错误日志并静默返回,不产生异常,调用方需自行通过连接状态回调保证时序。

性能与运维注意事项

  • 扫描功耗:系统扫描采用 SCAN_MODE_BALANCED 均衡模式,interval: 500 配合 10 秒级超时(scanTimeOut),避免长时间高占空比扫描耗电。
  • 设备列表全量上报:_onFound 每次变化都广播完整 BleDevice[],列表较大时业务层应做节流渲染;去重逻辑已尽量降低触发频率。
  • 系统设备轮询开销:_startSystemConnectedDeviceFound 每 500ms 调用一次 hidProfile.getConnectedDevices(),仅在配置开启且扫描进行中运行,扫描停止/超时即被清理。
  • 发数缓冲:数据发送经 BleSendDataHandler + BufferQueue 串行化,避免大报文(如 OTA 固件包)写入时被系统特征值写入速率限制打断;如需深入,参见 base/BaseSendDataHandler 与 ble/BleSendDataHandler。
  • 日志:模块使用 Log 工具类(TAG 为 BleImpl),扫描/连接关键路径均有 Log.d/i,错误统一 Log.e 打印 code + message,便于线上定位。

扩展点

  1. 新增扫描参数:扩展 BleScanSettingConfigure 字段并在 _startScan 的 ble.ScanOptions 中映射,即可开放更多系统扫描选项(如 dutyMode 切换为高性能模式)。
  2. 新增事件类型:在 IBleScan/IBleConnect 中增加事件常量与回调载荷类型,复用 callbacksMap 的 on/off 机制即可对外广播,无需改动分发框架。
  3. 替换发数策略:BleSendDataHandler 继承自 BaseSendDataHandler,可通过子类化覆盖分包大小、重试策略或写入时序。
  4. 通道扩展:在 BluetoothDevice 下新增子类并在 BluetoothManager 的门面方法中增加分支,即可接入新的传输通道(如已存在的 SppDevice/SppImpl)。

相关链接

  • BluetoothManager.ets(门面层)
  • BleImpl.ets(BLE 核心实现)
  • BleDevice.ets(BLE 设备模型)
  • IBleConnect.ets / IBleScan.ets(接口与事件定义)
  • base/BluetoothDevice.ets(设备基类)
  • 同架构其他页面:SPP 模块实现(spp/SppImpl.ets)、BluetoothOTAManager 与 OTA 升级流程
Prev
蓝牙抽象层与基础组件
Next
SPP 模块实现