8. BLE扫描开发:扫描API、回调、过滤、设置与低功耗策略

各位同学,今天我们来聊聊BLE扫描开发。这部分内容,说白了就是让你的手机“看到”周围的蓝牙设备。我刚开始做蓝牙项目时,以为扫描就是调个API完事,结果被功耗和兼容性问题折腾得够呛。今天我把这些坑都给你指出来。

8.1 扫描API:BluetoothLeScanner

在Android 5.0(API 21)之后,Google推出了BluetoothLeScanner,替代了旧的startLeScan()方法。我个人习惯用这个新API,原因很简单——它更灵活,而且支持扫描过滤和设置。

获取方式很简单:

BluetoothManager bluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
BluetoothAdapter bluetoothAdapter = bluetoothManager.getAdapter();
BluetoothLeScanner bluetoothLeScanner = bluetoothAdapter.getBluetoothLeScanner();

嗯,这里要注意:bluetoothAdapter可能为null,说明设备不支持蓝牙。另外,如果蓝牙没打开,getBluetoothLeScanner()也会返回null。我在项目中遇到过,用户手机蓝牙关闭时直接调用扫描,结果空指针崩溃——所以一定要做判空处理。

⚠️ 重要提醒: 调用扫描前,请确保:
  • 蓝牙已开启(bluetoothAdapter.isEnabled()
  • 已获取BLUETOOTH_SCAN权限(Android 12+)
  • 位置权限已授予(Android 10以下)

8.2 扫描回调:ScanCallback

扫描结果是通过回调返回的。你需要创建一个ScanCallback的子类,重写三个方法:

private final ScanCallback scanCallback = new ScanCallback() {
    @Override
    public void onScanResult(int callbackType, ScanResult result) {
        // 每次扫描到一个设备,就会回调这里
        BluetoothDevice device = result.getDevice();
        String name = device.getName();
        String address = device.getAddress();
        int rssi = result.getRssi();
        Log.d("BLE", "发现设备: " + name + " [" + address + "] 信号强度: " + rssi);
    }

    @Override
    public void onBatchScanResults(List<ScanResult> results) {
        // 批量扫描结果,一般用于低功耗模式
        for (ScanResult result : results) {
            // 处理每个结果
        }
    }

    @Override
    public void onScanFailed(int errorCode) {
        // 扫描失败,errorCode 说明原因
        Log.e("BLE", "扫描失败,错误码: " + errorCode);
    }
};

你想想看,onScanResult是高频回调,如果设备很多,这个回调会被疯狂调用。我曾经在一个商场里测试,周围有几十个蓝牙设备,手机直接卡顿——后来加了去重和频率限制才解决。

💡 个人经验:onScanResult中不要做耗时操作,比如数据库写入或网络请求。建议用Handler或RxJava把数据抛到后台线程处理。

8.3 扫描过滤:ScanFilter

为什么要过滤?说白了,就是减少无效回调,降低功耗。你想想,如果周围有100个设备,你只关心其中1个,那99次回调都是浪费。

ScanFilter支持多种过滤方式:

过滤类型 说明 示例
设备名称 按蓝牙名称匹配 new ScanFilter.Builder().setDeviceName("MyDevice").build()
设备地址 按MAC地址匹配 new ScanFilter.Builder().setDeviceAddress("AA:BB:CC:DD:EE:FF").build()
Service UUID 按服务UUID匹配 new ScanFilter.Builder().setServiceUuid(ParcelUuid.fromString("0000180F-0000-1000-8000-00805F9B34FB")).build()
制造商数据 按制造商ID过滤 new ScanFilter.Builder().setManufacturerData(0x004C, new byte[]{...}).build()

我个人最常用的是Service UUID过滤。为什么呢?因为很多蓝牙设备(比如心率计、温度计)都有标准的UUID,过滤后基本不会漏掉目标设备。

List<ScanFilter> filters = new ArrayList<>();
ScanFilter filter = new ScanFilter.Builder()
    .setServiceUuid(new ParcelUuid(UUID.fromString("0000180F-0000-1000-8000-00805F9B34FB")))
    .build();
filters.add(filter);
⚠️ 注意: 过滤条件越多,扫描结果越精确,但也可能漏掉设备。我建议先不加过滤做一次全量扫描,确认目标设备的特征后,再加过滤。

8.4 扫描设置:ScanSettings

ScanSettings决定了扫描的行为模式。它有三个核心参数:

  • 扫描模式: SCAN_MODE_LOW_POWER(低功耗)、SCAN_MODE_BALANCED(平衡)、SCAN_MODE_LOW_LATENCY(低延迟)
  • 回调类型: CALLBACK_TYPE_ALL_MATCHES(所有匹配)、CALLBACK_TYPE_FIRST_MATCH(首次匹配)、CALLBACK_TYPE_MATCH_LOST(丢失匹配)
  • 匹配模式: MATCH_MODE_AGGRESSIVE(激进,更快发现)、MATCH_MODE_STICKY(粘性,更稳定)
ScanSettings settings = new ScanSettings.Builder()
    .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
    .setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES)
    .setMatchMode(ScanSettings.MATCH_MODE_AGGRESSIVE)
    .build();

嗯,这里有个坑:低延迟模式虽然扫描快,但耗电也快。我做过测试,LOW_LATENCY模式连续扫描10分钟,电量消耗是LOW_POWER模式的3倍左右。所以,如果你只是定时扫描(比如每30秒扫一次),用BALANCED模式就够了。

8.5 低功耗扫描策略

这是本章的重点。BLE本身就是为了低功耗设计的,但如果你扫描策略不对,照样能把电池耗光。我总结了几条实战经验:

📌 核心策略:
  1. 不要持续扫描: 扫描10秒,停5秒,比持续扫描15秒更省电。
  2. 使用扫描过滤: 减少不必要的回调,CPU不用频繁唤醒。
  3. 合理设置扫描窗口: 扫描窗口越短,功耗越低,但可能漏掉设备。
  4. 利用批量回调: onBatchScanResultsonScanResult更省电。
  5. 及时停止扫描: 找到目标设备后,立刻调用stopScan()

我曾经做过一个蓝牙门禁项目,最初是持续扫描,结果手机半天就没电了。后来改成“扫描5秒→暂停10秒→再扫描5秒”的间歇模式,续航直接翻倍。你想想看,这个策略是不是很简单?但很多人就是想不到。

// 间歇扫描示例
private void startPeriodicScan() {
    handler.postDelayed(new Runnable() {
        @Override
        public void run() {
            bluetoothLeScanner.startScan(filters, settings, scanCallback);
            // 扫描5秒后停止
            handler.postDelayed(() -> {
                bluetoothLeScanner.stopScan(scanCallback);
                // 暂停10秒后再扫
                handler.postDelayed(this, 10000);
            }, 5000);
        }
    }, 0);
}
💡 避坑指南: 我曾经在Android 12上遇到一个问题——扫描回调突然不触发了。查了半天,原来是系统对后台扫描做了限制。解决方案是:如果应用在后台,使用PendingIntent方式的扫描,或者申请BLUETOOTH_SCAN权限并声明usesPermissionFlags="neverForLocation"

8.6 知识体系总览

下面这张图,是我自己整理的BLE扫描知识体系。你可以把它当作一个“扫描开发地图”,遇到问题回来看看,基本都能找到方向。

BLE扫描开发 BluetoothLeScanner ScanCallback ScanFilter ScanSettings 低功耗策略 实战避坑 getAdapter() 判空处理 onScanResult onScanFailed UUID过滤 名称过滤 扫描模式 回调类型 间歇扫描 及时停止 权限问题 后台限制

好了,关于BLE扫描开发的核心内容就这些。从API到回调,从过滤到设置,再到低功耗策略,每一步都有讲究。记住:扫描不是“扫到就行”,而是“扫得巧、扫得省”。


公众号:蓝海资料掘金营,微信deep3321