3、蓝牙适配器与设备发现:BluetoothAdapter的获取与初始化、开启/关闭蓝牙、扫描蓝牙设备(经典蓝牙+BLE)、扫描回调与设备过滤

各位同学,今天我们来聊聊蓝牙开发中最基础、也最绕不开的一个环节——蓝牙适配器与设备发现。说白了,就是怎么让你的App跟手机的蓝牙硬件“对上话”,然后找到周围有哪些设备可以连接。

我刚开始做蓝牙开发那会儿,觉得这步很简单,不就是调几个API嘛。结果真上手才发现,坑比想象的多。比如有的手机蓝牙没打开,有的权限没给,有的扫描回调死活不触发……嗯,今天我就把这些经验掰开揉碎了讲给你听。

3.1 BluetoothAdapter的获取与初始化

BluetoothAdapter,你可以把它理解成蓝牙硬件的“遥控器”。所有蓝牙操作,都得通过它来发号施令。

获取BluetoothAdapter的方式有两种,我个人习惯用第一种:

// 方式一:通过系统服务获取(推荐)
BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();

// 方式二:通过Context获取(不推荐,但某些场景下有用)
BluetoothManager bluetoothManager = (BluetoothManager) context.getSystemService(Context.BLUETOOTH_SERVICE);
BluetoothAdapter bluetoothAdapter = bluetoothManager.getAdapter();

这里有个关键点:getDefaultAdapter()返回null怎么办?我遇到过,那说明设备没有蓝牙硬件,或者模拟器上跑。所以拿到adapter后,一定要判空:

if (bluetoothAdapter == null) {
    // 设备不支持蓝牙,提示用户
    return;
}
我的习惯:在Application初始化时就获取BluetoothAdapter,然后通过单例持有。这样后续所有页面都能直接使用,不用重复获取。

3.2 开启/关闭蓝牙

拿到adapter后,第一件事通常是检查蓝牙是否开启。如果没开,你得引导用户去打开。

开启蓝牙有两种方式:

  1. 静默开启(需要系统权限)——直接调用bluetoothAdapter.enable(),但需要BLUETOOTH_ADMIN权限,且Android 10以后基本被限制。
  2. 用户授权开启(推荐)——通过Intent让用户手动确认。
// 检查蓝牙是否开启
if (!bluetoothAdapter.isEnabled()) {
    // 方式一:直接开启(不推荐,Android 10+可能失败)
    // bluetoothAdapter.enable();

    // 方式二:请求用户开启(推荐)
    Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
    startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
}

关闭蓝牙更简单:

bluetoothAdapter.disable();
注意:关闭蓝牙会影响其他正在使用蓝牙的应用。我曾经在一个项目中,因为测试代码里不小心调了disable(),导致同事的蓝牙耳机断连,被追着骂了一下午……所以生产环境慎用。

3.3 扫描蓝牙设备(经典蓝牙 + BLE)

扫描设备,是蓝牙开发的核心环节。经典蓝牙和BLE的扫描方式完全不同,我们分开讲。

3.4.1 经典蓝牙扫描

经典蓝牙扫描,说白了就是找那些传统的蓝牙设备,比如蓝牙耳机、音箱、车载系统。API很简单:

// 开始扫描
bluetoothAdapter.startDiscovery();

// 停止扫描
bluetoothAdapter.cancelDiscovery();

扫描结果通过BroadcastReceiver接收:

// 注册广播
IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
registerReceiver(receiver, filter);

// 广播接收器
private final BroadcastReceiver receiver = new BroadcastReceiver() {
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
        if (BluetoothDevice.ACTION_FOUND.equals(action)) {
            BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
            // 处理发现的设备
        }
    }
};

这里有个坑:startDiscovery()会扫描大约12秒,然后自动停止。如果你想提前停止,记得调cancelDiscovery()。我见过有人忘了调,结果扫描一直跑,电量哗哗地掉。

3.4.2 BLE扫描

BLE扫描跟经典蓝牙完全不同。它用的是BluetoothLeScanner,而且需要指定扫描回调:

// 获取BLE扫描器
BluetoothLeScanner bluetoothLeScanner = bluetoothAdapter.getBluetoothLeScanner();

// 创建扫描回调
private ScanCallback scanCallback = new ScanCallback() {
    @Override
    public void onScanResult(int callbackType, ScanResult result) {
        BluetoothDevice device = result.getDevice();
        int rssi = result.getRssi();
        byte[] scanRecord = result.getScanRecord().getBytes();
        // 处理扫描结果
    }

    @Override
    public void onScanFailed(int errorCode) {
        // 扫描失败处理
    }
};

// 开始扫描
bluetoothLeScanner.startScan(scanCallback);

// 停止扫描
bluetoothLeScanner.stopScan(scanCallback);
关键区别:经典蓝牙扫描用BroadcastReceiver,BLE扫描用Callback。前者是系统广播,后者是直接回调。我个人更推荐BLE的方式,因为更可控,而且能拿到RSSI信号强度。

3.5 扫描回调与设备过滤

扫描回调里,你会收到一堆设备。但很多设备你可能不感兴趣,比如那些信号弱的、名称奇怪的。这时候就需要过滤。

过滤的方式有两种:

  1. 在回调里过滤——收到结果后,根据条件判断是否展示。
  2. 使用ScanFilter(BLE专用)——在扫描前就指定过滤条件,系统帮你筛。

先看回调里过滤的例子:

// 在onScanResult中过滤
@Override
public void onScanResult(int callbackType, ScanResult result) {
    BluetoothDevice device = result.getDevice();
    String deviceName = device.getName();
    int rssi = result.getRssi();

    // 过滤条件:名称不为空,且信号强度大于-80dBm
    if (deviceName != null && !deviceName.isEmpty() && rssi > -80) {
        // 添加到设备列表
        deviceList.add(device);
    }
}

再看ScanFilter的方式,更优雅:

// 创建ScanFilter
List<ScanFilter> filters = new ArrayList<>();
ScanFilter filter = new ScanFilter.Builder()
    .setDeviceName("MyDevice")  // 按名称过滤
    .setServiceUuid(new ParcelUuid(UUID.fromString("0000180f-0000-1000-8000-00805f9b34fb")))  // 按服务UUID过滤
    .build();
filters.add(filter);

// 创建扫描设置
ScanSettings settings = new ScanSettings.Builder()
    .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)  // 低延迟模式,扫描更快
    .build();

// 开始带过滤的扫描
bluetoothLeScanner.startScan(filters, settings, scanCallback);
避坑指南:我曾经在过滤时只按设备名称过滤,结果发现有些设备名称是空的,或者名称里带不可见字符。后来我改成同时过滤名称和信号强度,效果好了很多。另外,ScanFilter的setDeviceName是精确匹配,不是模糊匹配,要注意。

3.6 知识体系总览

为了让你更直观地理解这一章的内容,我画了一张图,把蓝牙适配器与设备发现的整个流程串起来:

蓝牙适配器与设备发现 — 知识体系 BluetoothAdapter 获取与初始化(getDefaultAdapter) 开启/关闭蓝牙 扫描设备 扫描回调与过滤 enable() / disable() 静默开启 vs 用户授权开启 经典蓝牙 startDiscovery() BLE startScan() 过滤方式 回调过滤 / ScanFilter 扫描结果 → 设备列表 → 连接

这张图把整个流程分成了五层:从最底层的BluetoothAdapter获取,到初始化、开关蓝牙、扫描设备,再到最后的回调过滤。你跟着这个流程走,基本不会迷路。

3.7 本章小结

这一章我们讲了三个核心点:

  • BluetoothAdapter的获取与初始化——记住判空,别在模拟器上跑。
  • 开启/关闭蓝牙——推荐用Intent让用户授权,别偷偷摸摸地开。
  • 扫描设备与过滤——经典蓝牙用BroadcastReceiver,BLE用Callback。过滤时别只看名称,信号强度也很重要。

嗯,内容就这些。下一章我们会深入蓝牙连接与通信,到时候会用到今天讲的扫描结果。记得把代码跑一遍,有问题随时问。


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