4、蓝牙适配器与设备发现:BluetoothAdapter的获取与初始化、开启蓝牙、扫描设备、处理扫描结果回调

蓝牙设备发现是Android蓝牙开发中最基础也最关键的一环。无论是经典蓝牙(BR/EDR)的配对连接,还是BLE(低功耗蓝牙)的GATT通信,第一步都必须通过BluetoothAdapter完成适配器获取、权限校验、蓝牙开启和扫描流程。本章将围绕BluetoothAdapter的初始化、蓝牙开启、设备扫描及结果回调四个核心环节展开,结合Android 12(API 31)之后的权限模型变化,给出可直接落地的工程实践方案。

蓝牙适配器与设备发现核心链路 应用层 BluetoothAdapter getDefaultAdapter() startDiscovery() 调用 权限校验层 BLUETOOTH_SCAN BLUETOOTH_CONNECT ACCESS_FINE_LOCATION 授权 系统蓝牙服务 BluetoothManager BluetoothAdapter 系统级扫描调度 回调 蓝牙芯片 BR/EDR BLE 扫描结果回调链路 BroadcastReceiver ACTION_FOUND ACTION_DISCOVERY_FINISHED LeScanCallback onLeScan() API 21+ ScanCallback onScanResult() API 21+ 推荐 数据处理 过滤/去重/排序 更新UI列表 关键注意事项 • 经典蓝牙扫描需 ACCESS_FINE_LOCATION(Android 12前)/ BLUETOOTH_SCAN(Android 12+) • BLE扫描建议使用 startScan() 而非 startLeScan(),后者已废弃 • 扫描回调需在主线程注册,耗时操作请移入子线程 • 发现完成后必须调用 cancelDiscovery() 释放系统资源

4.1 BluetoothAdapter的获取与初始化

核心原理:BluetoothAdapter是Android蓝牙框架的入口对象,代表本机蓝牙硬件。系统通过BluetoothManager服务向应用层暴露适配器实例。获取方式有两种:一是通过BluetoothManager.getAdapter(),二是直接调用BluetoothAdapter.getDefaultAdapter()。前者在API 18(Android 4.3)之后推荐使用,因为它能同时获取系统蓝牙管理服务,便于后续操作;后者是传统方式,在API 31之后部分场景下可能返回null(如模拟器或没有蓝牙硬件的设备)。

初始化流程:

  1. 在AndroidManifest.xml中声明蓝牙权限(根据targetSdkVersion选择不同权限组合)。
  2. 获取BluetoothAdapter实例,并判断是否为null。
  3. 检查蓝牙是否开启,若未开启则请求用户授权。
  4. 注册广播接收器监听蓝牙状态变化(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所需权限动态申请
经典蓝牙扫描≤30BLUETOOTH, BLUETOOTH_ADMIN, ACCESS_FINE_LOCATION仅定位权限
经典蓝牙扫描≥31BLUETOOTH_SCAN, BLUETOOTH_CONNECT, ACCESS_FINE_LOCATION全部动态申请
BLE扫描≤30BLUETOOTH, BLUETOOTH_ADMIN, ACCESS_FINE_LOCATION仅定位权限
BLE扫描≥31BLUETOOTH_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值波动大,且经典蓝牙音箱无法被发现。

分析判断:

  1. BLE扫描窗口设置过短(SCAN_MODE_LOW_POWER),导致漏检。
  2. 未同时启动经典蓝牙扫描,导致音箱设备缺失。
  3. 回调中直接进行数据库写入,造成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混乱。