4、蓝牙适配器与设备发现:BluetoothAdapter的获取与初始化、开启蓝牙、扫描设备、处理扫描结果回调
蓝牙设备发现是Android蓝牙开发中最基础也最关键的一环。无论是经典蓝牙(BR/EDR)的配对连接,还是BLE(低功耗蓝牙)的GATT通信,第一步都必须通过BluetoothAdapter完成适配器获取、权限校验、蓝牙开启和扫描流程。本章将围绕BluetoothAdapter的初始化、蓝牙开启、设备扫描及结果回调四个核心环节展开,结合Android 12(API 31)之后的权限模型变化,给出可直接落地的工程实践方案。
4.1 BluetoothAdapter的获取与初始化
核心原理:BluetoothAdapter是Android蓝牙框架的入口对象,代表本机蓝牙硬件。系统通过BluetoothManager服务向应用层暴露适配器实例。获取方式有两种:一是通过BluetoothManager.getAdapter(),二是直接调用BluetoothAdapter.getDefaultAdapter()。前者在API 18(Android 4.3)之后推荐使用,因为它能同时获取系统蓝牙管理服务,便于后续操作;后者是传统方式,在API 31之后部分场景下可能返回null(如模拟器或没有蓝牙硬件的设备)。
初始化流程:
- 在AndroidManifest.xml中声明蓝牙权限(根据targetSdkVersion选择不同权限组合)。
- 获取BluetoothAdapter实例,并判断是否为null。
- 检查蓝牙是否开启,若未开启则请求用户授权。
- 注册广播接收器监听蓝牙状态变化(ACTION_STATE_CHANGED)。
// 推荐方式:通过BluetoothManager获取
BluetoothManager bluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
BluetoothAdapter bluetoothAdapter = bluetoothManager.getAdapter();
// 传统方式
BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
if (adapter == null) {
// 设备不支持蓝牙
return;
}
if (!adapter.isEnabled()) {
Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
}
权限声明差异(Android 12+):当targetSdkVersion ≥ 31时,必须使用BLUETOOTH_SCAN、BLUETOOTH_CONNECT和BLUETOOTH_ADVERTISE这三个新权限,并在代码中动态申请。旧权限BLUETOOTH和BLUETOOTH_ADMIN仅用于Android 11及以下版本。若未正确声明,扫描将直接抛出SecurityException。
| 场景 | targetSdkVersion | 所需权限 | 动态申请 |
|---|---|---|---|
| 经典蓝牙扫描 | ≤30 | BLUETOOTH, BLUETOOTH_ADMIN, ACCESS_FINE_LOCATION | 仅定位权限 |
| 经典蓝牙扫描 | ≥31 | BLUETOOTH_SCAN, BLUETOOTH_CONNECT, ACCESS_FINE_LOCATION | 全部动态申请 |
| BLE扫描 | ≤30 | BLUETOOTH, BLUETOOTH_ADMIN, ACCESS_FINE_LOCATION | 仅定位权限 |
| BLE扫描 | ≥31 | BLUETOOTH_SCAN, BLUETOOTH_CONNECT, ACCESS_FINE_LOCATION | 全部动态申请 |
4.2 开启蓝牙的工程实践
操作流程:开启蓝牙有两种方式:一是直接调用adapter.enable()(需要BLUETOOTH_ADMIN权限,且Android 13之后不再推荐),二是通过系统Intent请求用户确认。推荐使用Intent方式,因为系统会弹出明确的授权对话框,用户体验更规范,且无需额外权限。
// 方式一:直接开启(不推荐,Android 13+受限)
if (adapter != null && !adapter.isEnabled()) {
adapter.enable(); // 需要BLUETOOTH_ADMIN权限
}
// 方式二:请求用户确认(推荐)
if (!adapter.isEnabled()) {
Intent enableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(enableIntent, REQUEST_ENABLE_BT);
}
// 监听开启结果
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == REQUEST_ENABLE_BT) {
if (resultCode == RESULT_OK) {
// 蓝牙已开启,开始扫描
} else {
// 用户拒绝,提示并退出
}
}
}
状态监听:注册BroadcastReceiver监听BluetoothAdapter.ACTION_STATE_CHANGED,在EXTRA_STATE中获取蓝牙状态(STATE_ON/STATE_OFF/STATE_TURNING_ON/STATE_TURNING_OFF)。这样可以避免在蓝牙尚未完全开启时调用扫描方法导致失败。
4.3 扫描设备:经典蓝牙与BLE的差异
核心原理:经典蓝牙使用startDiscovery()进行异步扫描,扫描结果通过BroadcastReceiver返回;BLE则使用startLeScan()(已废弃)或startScan()(推荐)配合ScanCallback回调。两者的扫描机制完全不同:经典蓝牙扫描会持续约12秒,而BLE扫描可以自定义扫描窗口和间隔,更灵活。
经典蓝牙扫描实现:
// 注册接收器
IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
registerReceiver(receiver, filter);
// 开始扫描
if (adapter.isDiscovering()) {
adapter.cancelDiscovery();
}
adapter.startDiscovery();
// 接收器处理
private final BroadcastReceiver receiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (BluetoothDevice.ACTION_FOUND.equals(action)) {
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
short rssi = intent.getShortExtra(BluetoothDevice.EXTRA_RSSI, Short.MIN_VALUE);
// 处理设备信息
} else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(action)) {
// 扫描结束
}
}
};
BLE扫描实现(推荐):
private final 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) {
// 处理扫描失败
}
};
// 配置扫描设置
ScanSettings settings = new ScanSettings.Builder()
.setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
.build();
// 配置过滤条件(可选)
List<ScanFilter> filters = new ArrayList<>();
filters.add(new ScanFilter.Builder().setDeviceName("MyDevice").build());
// 开始扫描
bluetoothAdapter.getBluetoothLeScanner().startScan(filters, settings, scanCallback);
// 停止扫描
bluetoothAdapter.getBluetoothLeScanner().stopScan(scanCallback);
扫描策略对比:
- 经典蓝牙:适合发现传统蓝牙设备(耳机、音箱等),扫描时间固定,无法自定义。
- BLE:适合IoT设备、穿戴设备,可配置扫描窗口(如低功耗模式),支持过滤和定向扫描。
- 混合场景:若需同时发现两类设备,需分别调用两种扫描方式,并注意系统资源占用。
4.4 处理扫描结果回调与实战案例
回调机制:扫描结果通过回调接口异步返回。经典蓝牙依赖BroadcastReceiver,BLE依赖ScanCallback。回调运行在Binder线程池,不能直接更新UI,需通过Handler切换到主线程。同时要注意回调的并发问题,避免在回调中执行耗时操作。
案例拆解:智能家居APP的设备发现模块
背景与约束:某智能家居APP需要同时发现周边的BLE温湿度传感器和经典蓝牙音箱。目标设备数量约50个,扫描环境为室内,存在Wi-Fi和2.4G信号干扰。要求扫描时间不超过15秒,UI列表实时刷新。
初始现象:使用单一BLE扫描时,发现部分设备RSSI值波动大,且经典蓝牙音箱无法被发现。
分析判断:
- BLE扫描窗口设置过短(SCAN_MODE_LOW_POWER),导致漏检。
- 未同时启动经典蓝牙扫描,导致音箱设备缺失。
- 回调中直接进行数据库写入,造成ANR风险。
实际操作步骤:
// 1. 设置合理的扫描参数
ScanSettings settings = new ScanSettings.Builder()
.setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
.setReportDelay(0) // 实时回调
.build();
// 2. 并行启动两种扫描
adapter.startDiscovery(); // 经典蓝牙
bluetoothAdapter.getBluetoothLeScanner().startScan(filters, settings, scanCallback);
// 3. 回调中仅做数据收集,通过Handler更新UI
private final Handler mainHandler = new Handler(Looper.getMainLooper());
@Override
public void onScanResult(int callbackType, ScanResult result) {
mainHandler.post(() -> {
// 更新UI列表
deviceListAdapter.addDevice(result.getDevice(), result.getRssi());
});
}
// 4. 扫描结束后统一处理
@Override
public void onScanComplete() {
mainHandler.post(() -> {
// 停止刷新动画,显示结果
progressBar.setVisibility(View.GONE);
});
}
结果对比:调整后,BLE设备发现率从82%提升至97%,经典蓝牙音箱全部被发现。UI刷新流畅,无ANR发生。
风险与代价:低延迟模式会增加功耗,实测电量消耗提升约15%。可通过在扫描结束后立即调用stopScan()和cancelDiscovery()来降低影响。
常见误区:
- 忘记在onDestroy中注销Receiver和停止扫描,导致内存泄漏。
- 在Android 12+设备上未动态申请BLUETOOTH_SCAN权限,直接调用startScan()崩溃。
- 扫描回调中直接操作数据库或网络请求,造成主线程阻塞。
- 未处理扫描失败回调(onScanFailed),导致用户无感知。
总结复盘:设备发现模块的核心在于权限管理、扫描策略选择和回调处理。建议将扫描逻辑封装为独立的DeviceDiscoveryManager类,统一管理扫描生命周期。对于生产环境,务必在真机上测试不同Android版本的兼容性,特别是权限模型变化较大的Android 12和Android 13。此外,扫描结果应做去重和排序处理,避免重复设备导致UI混乱。