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;
}
3.2 开启/关闭蓝牙
拿到adapter后,第一件事通常是检查蓝牙是否开启。如果没开,你得引导用户去打开。
开启蓝牙有两种方式:
- 静默开启(需要系统权限)——直接调用
bluetoothAdapter.enable(),但需要BLUETOOTH_ADMIN权限,且Android 10以后基本被限制。 - 用户授权开启(推荐)——通过Intent让用户手动确认。
// 检查蓝牙是否开启
if (!bluetoothAdapter.isEnabled()) {
// 方式一:直接开启(不推荐,Android 10+可能失败)
// bluetoothAdapter.enable();
// 方式二:请求用户开启(推荐)
Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
}
关闭蓝牙更简单:
bluetoothAdapter.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);
3.5 扫描回调与设备过滤
扫描回调里,你会收到一堆设备。但很多设备你可能不感兴趣,比如那些信号弱的、名称奇怪的。这时候就需要过滤。
过滤的方式有两种:
- 在回调里过滤——收到结果后,根据条件判断是否展示。
- 使用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);
setDeviceName是精确匹配,不是模糊匹配,要注意。
3.6 知识体系总览
为了让你更直观地理解这一章的内容,我画了一张图,把蓝牙适配器与设备发现的整个流程串起来:
这张图把整个流程分成了五层:从最底层的BluetoothAdapter获取,到初始化、开关蓝牙、扫描设备,再到最后的回调过滤。你跟着这个流程走,基本不会迷路。
3.7 本章小结
这一章我们讲了三个核心点:
- BluetoothAdapter的获取与初始化——记住判空,别在模拟器上跑。
- 开启/关闭蓝牙——推荐用Intent让用户授权,别偷偷摸摸地开。
- 扫描设备与过滤——经典蓝牙用BroadcastReceiver,BLE用Callback。过滤时别只看名称,信号强度也很重要。
嗯,内容就这些。下一章我们会深入蓝牙连接与通信,到时候会用到今天讲的扫描结果。记得把代码跑一遍,有问题随时问。
公众号:蓝海资料掘金营,微信deep3321