4、BLE扫描基础:扫描参数配置、扫描结果回调、扫描过滤策略、扫描功耗优化
BLE扫描,说白了就是设备在找周围谁在“喊话”。
我刚开始做蓝牙项目时,觉得扫描嘛,不就是打开接口等数据?结果一跑起来,功耗高得吓人,回调乱成一锅粥。后来才明白,扫描这件事,参数配不好,后面全是坑。
4.1 扫描参数配置
鸿蒙的BLE扫描接口,核心就几个参数。但每个参数背后,都有讲究。
// 鸿蒙BLE扫描参数结构体
struct BleScanParams {
uint16_t scanInterval; // 扫描间隔,单位0.625ms
uint16_t scanWindow; // 扫描窗口,单位0.625ms
uint8_t scanType; // 0:被动扫描 1:主动扫描
uint8_t scanPhy; // 1M、2M、Coded
uint8_t scanFilterPolicy; // 过滤策略
};
scanInterval 和 scanWindow,这两个是功耗的关键。
scanWindow 必须 ≤ scanInterval。我见过有人把窗口设得比间隔还大,结果扫描直接失败。嗯,这其实是协议栈的硬性要求。
| 场景 | scanInterval | scanWindow | 说明 |
|---|---|---|---|
| 高实时性 | 30 (18.75ms) | 30 (18.75ms) | 连续扫描,功耗高 |
| 平衡模式 | 80 (50ms) | 40 (25ms) | 我项目中最常用的配置 |
| 低功耗 | 320 (200ms) | 32 (20ms) | 适合后台扫描 |
scanType,主动扫描 vs 被动扫描。
主动扫描会发扫描请求,让对方回复扫描响应。被动扫描只监听广播包。你想想看,主动扫描能拿到更多数据,但功耗也更高。我建议:如果只需要设备名和广播数据,被动扫描就够了。
scanPhy,物理层选择。
1M PHY最通用,2M PHY速率高但距离短,Coded PHY距离远但速率低。我在做室内定位项目时,就吃过Coded PHY的亏——距离是远了,但扫描周期变长,功耗反而没降下来。
4.2 扫描结果回调
扫描结果回调,是BLE扫描的“出口”。所有找到的设备,都会从这里冒出来。
// 注册扫描回调
BleCentralManager.registerBleScanCallback(new BleScanCallback() {
@Override
public void onScanResult(BleScanResult result) {
// 每个设备扫描到都会回调
String deviceAddr = result.getDevice().getDeviceAddr();
byte[] scanRecord = result.getScanRecord();
int rssi = result.getRssi();
// 我个人习惯在这里做初步过滤
if (rssi < -90) {
return; // 信号太弱,直接丢弃
}
}
@Override
public void onScanFailed(int errorCode) {
// 扫描失败回调
Log.e("BLE", "扫描失败,错误码:" + errorCode);
}
});
这里有个细节:onScanResult 回调频率很高。如果周围有几十个设备,每秒可能回调上百次。我曾经在回调里做数据库写入,结果手机直接卡死。后来改成先缓存到队列,再批量处理。
scanRecord 解析,这是另一个坑。
广播数据包是 TLV 格式(Type-Length-Value)。鸿蒙 SDK 提供了解析工具类,但有些厂商的广播包不标准。我曾经遇到一个设备,把设备名放在了自定义字段里,用标准解析拿不到。最后只能手动解析原始字节流。
// 手动解析广播包示例
private void parseScanRecord(byte[] scanRecord) {
int index = 0;
while (index < scanRecord.length) {
int length = scanRecord[index] & 0xFF;
if (length == 0) break;
int type = scanRecord[index + 1] & 0xFF;
byte[] data = Arrays.copyOfRange(scanRecord, index + 2, index + length + 1);
switch (type) {
case 0x09: // 完整设备名
case 0x08: // 简写设备名
String name = new String(data, StandardCharsets.UTF_8);
break;
// 其他类型...
}
index += length + 1;
}
}
4.3 扫描过滤策略
不设过滤,回调会多到让你怀疑人生。鸿蒙提供了几种过滤方式。
UUID 过滤,最常用。
只扫描包含特定 Service UUID 的设备。比如你只关心心率设备,就过滤 0x180D。
// 设置UUID过滤
BleScanFilter filter = new BleScanFilter.Builder()
.setServiceUuid(new UUID(0x180D, 0x00001000800000805F9B34FB))
.build();
List<BleScanFilter> filters = new ArrayList<>();
filters.add(filter);
centralManager.startScan(filters, scanParams, callback);
设备地址过滤,适合连接已知设备。
我做过一个项目,需要同时连接多个同型号设备。用地址过滤,每个设备单独扫描,互不干扰。
RSSI 阈值过滤,简单粗暴。
信号太弱的设备,连上了也容易断。我一般设 -80dBm 为阈值,低于这个值直接忽略。
4.4 扫描功耗优化
功耗优化,是BLE开发的重头戏。我见过不少产品,功能都实现了,但电池撑不过一天。嗯,问题多半出在扫描上。
策略一:间歇扫描
不要一直扫。扫几秒,停几秒。比如扫3秒,停10秒。这样平均功耗能降70%以上。
// 间歇扫描示例
private void startPeriodicScan() {
// 扫描3秒
centralManager.startScan(callback);
new Handler().postDelayed(() -> {
// 停止扫描
centralManager.stopScan(callback);
// 等待10秒后再次扫描
new Handler().postDelayed(this::startPeriodicScan, 10000);
}, 3000);
}
策略二:动态调整扫描窗口
刚启动时用大窗口快速发现设备,发现后用最小窗口维持连接。我曾经在智能门锁项目里用这个策略,待机功耗从 2mA 降到了 0.3mA。
策略三:利用扫描过滤减少回调
回调越少,CPU唤醒次数越少,功耗越低。把过滤条件设严一点,只关心你需要的设备。
核心原则:能不扫就不扫,能少扫就少扫,能过滤就过滤。
策略四:选择合适的 PHY
Coded PHY 虽然距离远,但扫描时间更长。如果设备距离不远,用 1M PHY 就够了。我测试过,同样场景下,1M PHY 比 Coded PHY 省电约 40%。
最后说一句:功耗优化没有银弹。每个项目都要根据实际场景调参。我的习惯是:先跑一周日志,分析扫描行为,再针对性优化。你想想看,连数据都没有,怎么优化?
好了,扫描基础就这些。下一章我们聊聊连接参数和MTU协商,那又是另一片天地。