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 协议报文。本模块的核心设计目标有三:
- 隔离系统 API 差异:将 OpenHarmony 的
ble、access、connection、hid等系统能力封装为一组面向业务的接口(IBleScan、IBleConnect),上层无需感知系统 API 细节。 - 事件驱动与解耦:通过
callbacksMap实现发布/订阅模式,扫描状态、设备发现、连接状态、MTU 变化、特征值变化均以回调方式对外广播,业务层只需on/off订阅。 - 设备类型多态分发:
BluetoothManager作为门面,依据设备实例类型(BleDevicevsSppDevice)或通讯方式("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.ets | BLE 核心实现:扫描、连接、MTU、服务发现、发数入口、事件分发 |
ble/BleDevice.ets | BLE 设备模型,扩展 BluetoothDevice |
ble/BleScanSettingConfigure.ets | 扫描参数配置(超时、是否包含系统已连接设备等) |
ble/BleConnectSettingConfigure.ets | 连接参数配置(目标 MTU 等) |
ble/BleSendDataHandler.ets | BLE 发数处理器(继承 BaseSendDataHandler,含分包/队列) |
ble/IBleScan.ets | BLE 扫描接口与事件类型(ScanEventType、ScanState) |
ble/IBleConnect.ets | BLE 连接接口与事件类型(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_CHANGE | ScanStateInfo | 扫描开始 / 失败 / 结束 |
ScanEventType.SCAN_DEVICE_FIND | BleDevice[] | 发现设备(全量列表) |
BaseConnectEventTypeConstant.CONNECT_STATE_CHANGE | ConnectStateInfo<BleDevice> | 连接成功 / 失败 |
BleConnectEventTypeConstant.CONNECT_MTU_CHANGE | MTUInfo | MTU 协商结果 |
BleConnectEventTypeConstant.CONNECT_BLE_CHARACTERISTIC_CHANGE | BLECharacteristicInfo | 远端特征值通知 |
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
流程要点:
- 状态短路:已连接直接回调
success;连接中则回调fail并携带ERROR_IS_CONNECTING,防止重复建链。 - 创建 GATT 客户端:
ble.createGattClientDevice(device.deviceId)生成系统级连接对象并挂到device.gattClientDevice,随后推入_connectingDeviceArray。 - MTU 协商:
onConnectStateChange收到STATE_CONNECTED后调用setBLEMtuSize(that._connectConfigure.mtu);onMtuChange回调里用setMtuTimeout定时器做超时保护,仅当定时器尚未触发时把device.mtu = mtu并进入服务初始化(见 BleImpl.ets)。 - 服务初始化与成功上报:
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)的按序组合,方法签名与源码一致;具体业务编排请参考上层页面代码。
配置选项
| 配置类 | 选项 | 类型 | 默认值 | 说明 |
|---|---|---|---|---|
BleScanSettingConfigure | scanTimeOut | number | 由配置类定义(具体值见 BleScanSettingConfigure.ets) | 扫描超时(毫秒),startScan(scanTimeOut) 可临时覆盖 |
BleScanSettingConfigure | isContainSystemsConnectedDevice | boolean | 由配置类定义 | 是否在扫描中合并系统已连接(HID)设备 |
BleConnectSettingConfigure | mtu | number | 由配置类定义(BleDevice.mtu 初始为 23) | 连接后请求的目标 MTU 大小,经 setBLEMtuSize 协商 |
BluetoothManager | communicationWay | BluetoothDeviceType | "BLE" | 未显式指定类型时扫描/查询类操作的默认通道 |
说明:
BleScanSettingConfigure与BleConnectSettingConfigure的具体默认值位于各自文件内,本次文档未展开读取,使用前请以源码为准。
API 参考
BleImpl(实现 IBleScan, IBleConnect)
| 方法 | 签名 | 说明 |
|---|---|---|
on | on(type, callback): void | 订阅事件(重载支持 5 类事件),重复订阅同一类型会追加回调 |
off | off(type, callback?): void | 退订;不传 callback 时清空该类型全部回调 |
isScanning | isScanning(): boolean | 当前是否正在扫描 |
startScan | startScan(scanTimeOut?: number): void | 开始扫描;已在扫描则刷新列表与计时 |
refreshScan | refreshScan(): void | 清空设备列表并重新计时(仅扫描中有效) |
stopScan | stopScan(): void | 停止扫描并广播 SCAN_STATE_FINISH |
getScanSettingConfigure | getScanSettingConfigure(): BleScanSettingConfigure | 获取扫描配置引用 |
setScanSettingConfigure | setScanSettingConfigure(cfg: BleScanSettingConfigure): void | 替换扫描配置 |
connect | connect(device: BleDevice, success?, fail?): void | 建立 GATT 连接(含 MTU/服务初始化) |
disconnect | disconnect(device: BleDevice): void | 断开连接并清理状态 |
isConnecting | isConnecting(device: BleDevice): boolean | 设备是否处于连接中 |
isConnected | isConnected(device: BleDevice): boolean | 设备是否已连接 |
getConnectedDevice | getConnectedDevice(): BleDevice[] | undefined | 获取已连接设备列表 |
sendData | sendData(device: BleDevice, serviceId: string, characteristicId: string, data: Uint8Array): void | 向指定特征值发送数据 |
失败回调参数:fail 回调携带 BusinessError,其中 code 取值来自 BluetoothErrorConstant(如 ERROR_IS_CONNECTING),message 为人类可读描述。
BluetoothManager(门面)
| 方法 | 签名 | 说明 |
|---|---|---|
connect | connect(device: BluetoothDevice, success?, fail?): void | 按 instanceof 路由到 bleImpl/sppImpl |
disconnect | disconnect(device: BluetoothDevice): void | 按类型路由断开 |
getConnectedDevice | getConnectedDevice(type?): BleDevice[] | SppDevice[] | undefined | 按 communicationWay 或显式类型查询 |
isConnecting / isConnected | (device): boolean | 按类型路由查询状态 |
isScanning / startScan | (type?): boolean / (scanTimeOut?, type?): void | 按通道执行扫描操作 |
bleImpl / sppImpl | getter | 直接访问底层实现实例 |
失败模式、边界情况与并发
已连接的重复连接(幂等短路)
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,便于线上定位。
扩展点
- 新增扫描参数:扩展
BleScanSettingConfigure字段并在_startScan的ble.ScanOptions中映射,即可开放更多系统扫描选项(如dutyMode切换为高性能模式)。 - 新增事件类型:在
IBleScan/IBleConnect中增加事件常量与回调载荷类型,复用callbacksMap的on/off机制即可对外广播,无需改动分发框架。 - 替换发数策略:
BleSendDataHandler继承自BaseSendDataHandler,可通过子类化覆盖分包大小、重试策略或写入时序。 - 通道扩展:在
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 升级流程