1. Android蓝牙扫描基础与ScanCallback概述
在Android蓝牙开发中,设备发现是建立通信的第一步关键操作。不同于传统蓝牙的笨重扫描机制,BLE(蓝牙低功耗)引入了高效的ScanCallback接口,让开发者能够精准控制扫描过程。我经历过不少项目,从简单的设备连接到复杂的多设备组网,ScanCallback的正确使用往往是成败的关键。
Android蓝牙扫描主要分为两种模式:传统蓝牙的startDiscovery()和BLE的BluetoothLeScanner。前者适用于经典蓝牙设备发现,后者则是本文重点——专为BLE设备优化的扫描机制。当调用startScan()方法时,系统会返回三种不同类型的回调:
onScanResult:发现设备时触发,包含设备MAC地址、信号强度(RSSI)和广播数据包onBatchScanResults:批量返回扫描结果(需API 21+且启用批处理模式)onScanFailed:扫描失败时回调,需处理错误码如SCAN_FAILED_APPLICATION_REGISTRATION_FAILED
关键提示:从Android 6.0开始,必须同时获取
ACCESS_COARSE_LOCATION或ACCESS_FINE_LOCATION权限才能获取BLE扫描结果,这是很多新手容易踩的坑。
2. ScanCallback核心参数解析与技术细节
2.1 扫描结果数据结构解剖
每个ScanResult对象都携带了丰富的设备信息,我们需要理解其中每个字段的实战意义:
java复制public class ScanResult {
private BluetoothDevice device; // 设备对象,含MAC地址
private ScanRecord scanRecord; // 广播数据包(重要!)
private int rssi; // 信号强度(dBm)
private long timestampNanos; // 发现时间戳(纳秒)
// ...
}
特别是ScanRecord对象,它包含了设备广播的原始数据,通过getBytes()可以获取完整字节数组。常见广播数据类型包括:
- 0x01:设备标志位
- 0x09:完整设备名
- 0xFF:厂商自定义数据(常用于设备识别)
2.2 高级扫描配置策略
通过ScanSettings.Builder可以创建精细化的扫描策略:
java复制ScanSettings settings = new ScanSettings.Builder()
.setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY) // 扫描模式
.setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES) // 回调类型
.setMatchMode(ScanSettings.MATCH_MODE_AGGRESSIVE) // 匹配模式
.setNumOfMatches(ScanSettings.MATCH_NUM_MAX_ADVERTISEMENT) // 匹配数量
.setReportDelay(1000) // 报告延迟(ms)
.build();
各参数实战建议:
SCAN_MODE_LOW_LATENCY:即时通讯场景,但耗电高SCAN_MODE_BALANCED:常规应用的最佳选择MATCH_MODE_STICKY:减少重复设备回调
2.3 广播数据过滤技巧
使用ScanFilter可以大幅提升扫描效率,以下是典型配置示例:
java复制List<ScanFilter> filters = new ArrayList<>();
filters.add(new ScanFilter.Builder()
.setServiceUuid(ParcelUuid.fromString("0000FEED-0000-1000-8000-00805F9B34FB"))
.setDeviceName("MyBLE_Device")
.setManufacturerData(0x004C, new byte[]{0x02, 0x15}) // Apple iBeacon
.build());
实测发现,合理的过滤条件可以减少30%-50%的无用回调。但要注意过度过滤可能导致设备发现延迟。
3. ScanCallback实战优化与性能调优
3.1 扫描生命周期管理
良好的扫描管理应该遵循以下模式:
java复制// 开始扫描
try {
bluetoothLeScanner.startScan(filters, settings, scanCallback);
scanStartTime = SystemClock.elapsedRealtime();
} catch (SecurityException e) {
// 处理权限异常
}
// 建议的扫描持续时间
private static final long SCAN_PERIOD = 10000; // 10秒
// 停止扫描
handler.postDelayed(() -> {
bluetoothLeScanner.stopScan(scanCallback);
analyzeScanResults();
}, SCAN_PERIOD);
经验表明,周期性扫描(如扫描10秒,暂停5秒)比持续扫描更省电,且对用户体验影响较小。
3.2 信号处理与距离估算
RSSI值虽然可以反映设备距离,但需要特殊处理:
java复制// 简单的距离估算算法
public static double calculateDistance(int rssi, int txPower) {
return Math.pow(10d, (txPower - rssi) / 20d);
}
// 使用移动平均滤波处理信号波动
private static final int RSSI_SMOOTHING_WINDOW = 5;
private SparseArray<LinkedList<Integer>> deviceRssiHistory = new SparseArray<>();
private int getSmoothedRssi(int deviceHash, int newRssi) {
LinkedList<Integer> history = deviceRssiHistory.get(deviceHash);
if (history == null) {
history = new LinkedList<>();
deviceRssiHistory.put(deviceHash, history);
}
history.addLast(newRssi);
if (history.size() > RSSI_SMOOTHING_WINDOW) {
history.removeFirst();
}
return (int) history.stream().mapToInt(Integer::intValue).average().orElse(newRssi);
}
3.3 后台扫描限制与应对
从Android 8.0开始,后台扫描受到严格限制。解决方案包括:
- 使用Foreground Service并显示持续通知
- 利用JobScheduler定期唤醒执行扫描
- 针对特定品牌手机(如小米、华为)添加白名单设置
xml复制<!-- AndroidManifest.xml 中声明前台服务类型 -->
<service
android:name=".BluetoothScanService"
android:foregroundServiceType="connectedDevice" />
4. 典型问题排查与性能优化记录
4.1 常见错误代码处理
| 错误代码 | 含义 | 解决方案 |
|---|---|---|
| SCAN_FAILED_ALREADY_STARTED | 重复启动扫描 | 检查扫描状态,避免重复调用 |
| SCAN_FAILED_APPLICATION_REGISTRATION_FAILED | 应用注册失败 | 重启蓝牙适配器 |
| SCAN_FAILED_INTERNAL_ERROR | 系统内部错误 | 等待后重试或重启设备 |
| SCAN_FAILED_FEATURE_UNSUPPORTED | 特性不支持 | 降级扫描配置 |
4.2 连接稳定性优化
扫描后连接经常失败?试试这些技巧:
- 在
onScanResult中立即停止扫描再发起连接 - 添加重试机制(建议最多3次)
- 不同设备设置不同的连接超时(建议1500-3000ms)
java复制private void connectWithRetry(BluetoothDevice device, int retryCount) {
if (retryCount <= 0) return;
handler.postDelayed(() -> {
if (gatt == null) {
gatt = device.connectGatt(context, false, gattCallback);
connectWithRetry(device, retryCount - 1);
}
}, 500);
}
4.3 广播数据解析实战
解析厂商特定数据示例:
java复制byte[] manufacturerData = scanRecord.getManufacturerSpecificData(0x004C);
if (manufacturerData != null && manufacturerData.length >= 23) {
String uuid = bytesToHex(Arrays.copyOfRange(manufacturerData, 2, 18));
int major = (manufacturerData[18] & 0xff) << 8 | (manufacturerData[19] & 0xff);
int minor = (manufacturerData[20] & 0xff) << 8 | (manufacturerData[21] & 0xff);
int txPower = manufacturerData[22];
// iBeacon数据解析完成
}
5. 进阶技巧与新型蓝牙协议适配
5.1 Android 12+新特性应用
Android 12引入了蓝牙扫描优化:
- 新增
ScanSettings.PHY_LE_CODED支持远距离通信 - 添加
isConnectable()判断设备是否可连接 - 改进后台扫描配额管理
java复制if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
settings = new ScanSettings.Builder()
.setPhy(ScanSettings.PHY_LE_ALL_SUPPORTED)
.build();
}
5.2 多设备扫描策略
在智能家居场景中,需要同时扫描多种设备:
java复制// 为不同类型设备创建不同过滤器
Map<String, ScanFilter> deviceFilters = new HashMap<>();
deviceFilters.put("Thermometer", buildFilterForService(UUID_THERMOMETER));
deviceFilters.put("Light", buildFilterForService(UUID_LIGHT));
// 使用单个扫描实例处理多种设备
bluetoothLeScanner.startScan(
new ArrayList<>(deviceFilters.values()),
settings,
new UnifiedScanCallback()
);
5.3 蓝牙Mesh网络支持
虽然Android原生API不直接支持Mesh,但可以通过代理节点实现:
- 扫描特定Mesh代理广播包
- 通过GATT连接代理节点
- 使用厂商定义的Mesh协议通信
java复制// 识别Mesh代理设备
private boolean isMeshProxy(ScanRecord record) {
byte[] data = record.getManufacturerSpecificData(MANUFACTURER_ID);
return data != null && data.length > 3 && data[0] == 0x01;
}
在最近的一个智能楼宇项目中,我们通过合理配置ScanCallback参数,将设备发现时间从平均8秒降低到3秒以内。关键是在SCAN_MODE_LOW_LATENCY下配合精确的ScanFilter使用,同时处理好不同Android版本的权限差异。记得在Android 10及以上设备上,除了位置权限还需要在应用设置中手动开启位置服务开关,否则即使有权限也无法获取扫描结果。
