鸿蒙操作系统使用 Bluetooth Kit 实现 BLE 扫描与 GATT 连接实战
一、背景说明
低功耗蓝牙(BLE)是可穿戴设备、传感器、耳机配件类应用的基础连接能力。近期星闪耳返等近距连接功能在 HarmonyOS 7 上陆续上线,近距通信生态持续升温,而 BLE 仍是应用侧最通用的设备发现与数据传输通道。本文基于 Connectivity Kit 的 @ohos.bluetooth.ble 模块,完整实现"扫描周边 BLE 设备 → 发起 GATT 连接 → 读写特征值"全流程,代码可在 API 12 及以上(含 26.0.0)工程中运行。
二、技术原理
BLE 采用 GATT(通用属性协议)组织数据:设备端提供服务(Service),服务下包含特征值(Characteristic),数据读写在特征值上进行。应用可作为两种角色:
- GATT 客户端:主动扫描广播、发起连接、读写对端特征值——手机 App 通常扮演此角色;
- GATT 服务端:向外发送广播并在被连接后提供数据——外设扮演此角色。
扫描与连接的数据流为:申请 ohos.permission.ACCESS_BLUETOOTH(user_grant 用户授权权限)→ ble.startBLEScan 发起扫描(或 API 15+ 的 createBleScanner 多路扫描)→ on('BLEDeviceFind') 回调拿到设备地址 → ble.createGattClientDevice 创建客户端 → connect() 建链 → getServices() 发现服务 → 读写特征值 / 订阅 Notify。连接成功后需把 MTU 调大(setBLEMtuSize),单次写入的数据量受 MTU-3 字节限制。
三、实现步骤
module.json5 中声明权限(ACCESS_BLUETOOTH 为用户授权权限,需运行时弹窗申请):
{
"module": {
"requestPermissions": [
{ "name": "ohos.permission.ACCESS_BLUETOOTH", "reason": "$string:bt_reason" }
]
}
}
新建 src/main/ets/ble/BleManager.ets(完整代码,扫描 + 连接 + 读写特征值封装):
import { ble } from '@kit.ConnectivityKit';
import { permissions } from '@kit.AbilityKit';
import { common } from '@kit.AbilityKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
const TAG: string = 'BleManager';
// 自定义服务与特征值 UUID(与对端设备约定一致)
const SERVICE_UUID: string = '00001888-0000-1000-8000-00805f9b34fb';
const CHAR_UUID: string = '00002a37-0000-1000-8000-00805f9b34fb';
export class BleManager {
private client: ble.GattClientDevice | null = null;
// 运行时申请用户授权权限
async requestBlePermission(context: common.UIAbilityContext): Promise<boolean> {
try {
const result = await permissions.requestPermissionsFromUser(context, {
permissions: ['ohos.permission.ACCESS_BLUETOOTH'],
requestPermissions: ['ohos.permission.ACCESS_BLUETOOTH']
});
return result.authResults.every(r => r === 0);
} catch (err) {
const e: BusinessError = err as BusinessError;
hilog.error(TAG, 'requestPermission failed: %{public}d %{public}s', e.code, e.message);
return false;
}
}
// 扫描指定服务的 BLE 设备
startScan(onDevice: (deviceId: string, name: string) => void): void {
try {
ble.on('BLEDeviceFind', (results: Array<ble.ScanResult>) => {
for (const r of results) {
const name = r.deviceName ?? '';
hilog.info(TAG, 'found: %{public}s %{public}s', r.deviceId, name);
onDevice(r.deviceId, name);
}
});
const filter: ble.ScanFilter = { serviceUuid: SERVICE_UUID };
ble.startBLEScan([filter], { dutyMode: ble.ScanDuty.SCAN_MODE_LOW_POWER });
} catch (err) {
const e: BusinessError = err as BusinessError;
hilog.error(TAG, 'startScan failed: %{public}d %{public}s', e.code, e.message);
}
}
stopScan(): void {
try {
ble.off('BLEDeviceFind');
ble.stopBLEScan();
} catch (err) {
const e: BusinessError = err as BusinessError;
hilog.error(TAG, 'stopScan failed: %{public}d', e.code);
}
}
// 连接目标设备并发现服务
connect(deviceId: string): Promise<void> {
return new Promise((resolve, reject) => {
try {
this.client = ble.createGattClientDevice(deviceId);
this.client.on('BLEConnectionStateChange', (state: ble.BLEConnectionChangeParam) => {
hilog.info(TAG, 'state: %{public}d', state.state); // 0断开 2已连接
if (state.state === 0) { this.client = null; }
});
this.client.connect();
setTimeout(() => {
this.client?.getServices().then((services: Array<ble.GattService>) => {
hilog.info(TAG, 'services: %{public}d', services.length);
resolve();
}).catch((err: BusinessError) => reject(err));
}, 1500); // 等待建链完成后再发现服务
} catch (err) {
reject(err as BusinessError);
}
});
}
// 写特征值(单次写入长度 ≤ MTU-3 字节)
async writeData(data: Uint8Array): Promise<void> {
if (!this.client) { throw new Error('not connected'); }
await this.client.setBLEMtuSize(247);
const char: ble.BLECharacteristic = {
serviceUuid: SERVICE_UUID,
characteristicUuid: CHAR_UUID,
descriptorUuid: '00002902-0000-1000-8000-00805f9b34fb',
value: data.buffer
};
await this.client.writeCharacteristicValue(char, ble.GattWriteType.WRITE);
}
// 订阅特征值 Notify 接收对端数据
subscribeNotify(onData: (v: Uint8Array) => void): void {
if (!this.client) { return; }
const char: ble.BLECharacteristic = {
serviceUuid: SERVICE_UUID,
characteristicUuid: CHAR_UUID,
descriptorUuid: '00002902-0000-1000-8000-00805f9b34fb',
value: new ArrayBuffer(0)
};
this.client.setCharacteristicChangeNotification(char, true);
this.client.on('CharacteristicChange', (r: ble.CharacteristicChangeResponse) => {
onData(new Uint8Array(r.characteristic.value));
});
}
disconnect(): void {
try { this.client?.disconnect(); this.client = null; } catch (err) { }
}
}
四、应用场景示例
在 Index.ets 中串联"授权 → 扫描 → 连接 → 写入"流程:
import { BleManager } from '../ble/BleManager';
@Entry
@Component
struct Index {
private ble: BleManager = new BleManager();
@State log: string = '就绪';
build() {
Column({ space: 12 }) {
Text(this.log).fontSize(16)
Button('扫描并连接').onClick(async () => {
const ctx = getContext(this) as common.UIAbilityContext;
if (!await this.ble.requestBlePermission(ctx)) {
this.log = '蓝牙权限被拒绝'; return;
}
this.ble.startScan((id, name) => {
this.log = `发现设备 ${name} ${id}`;
this.ble.stopScan();
this.ble.connect(id)
.then(async () => {
this.log = '已连接,写入数据';
await this.ble.writeData(new Uint8Array([0x01, 0x02]));
})
.catch((e: BusinessError) => { this.log = `连接失败 ${e.code}`; });
});
})
Button('断开').onClick(() => { this.ble.disconnect(); this.log = '已断开'; })
}
.width('100%').height('100%').justifyContent(FlexAlign.Center)
}
}
五、技术要点
- 权限模型:ACCESS_BLUETOOTH 是 user_grant 权限,仅在 module.json5 声明不够,必须运行时调用
requestPermissionsFromUser,拒绝后所有扫描/连接调用抛 201; - 扫描互斥:
startBLEScan是单路扫描,重复调用前必须先stopBLEScan,否则抛 2900001;需要并行多路扫描时用 API 15+ 的ble.createBleScanner创建独立实例; - 蓝牙未开:调用前可检查蓝牙状态,未开启抛 2900003,应引导用户去设置页开启;
- 建链时序:
connect()后立即getServices()可能拿不到服务,需等待连接状态回调或适当延时后再发现服务; - MTU 限制:默认 MTU 较小,写入大数据前先
setBLEMtuSize,且单次不超过 MTU-3 字节,超长数据需分包发送; - 操作串行化:GATT 单链路上的读写与订阅操作必须等上一个 Promise 完成再发起下一个,并发调用抛 2900011(operation busy)。
六、扩展建议
若需要同时维持扫描与连接(如后台持续发现新配件设备),改用多路扫描:
const scanner = ble.createBleScanner();
scanner.startScan([{ serviceUuid: SERVICE_UUID }], { dutyMode: ble.ScanDuty.SCAN_MODE_LOW_POWER });
scanner.on('BLEDeviceFind', (r: Array<ble.ScanResult>) => { /* 处理 */ });
26.0.0 起 createGattClientDevice 还支持传入 GattSetting 配置连接参数(超时、传输类型等),自动重连场景可在 connect 时设置 autoConnect: true,设备回到广播范围后系统会自动建链。
结语
本文用约 120 行 ArkTS 完成 BLE 从权限申请、设备扫描到 GATT 连接、特征值读写与 Notify 订阅的完整闭环,核心是 startBLEScan → createGattClientDevice → connect 三步链路加 MTU 与操作时序控制。掌握这条链路即可支撑耳机配对、传感器采集等绝大多数近距场景的开发。
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐



所有评论(0)