蓝牙适配器初始化与设备发现流程

本章进入实际编码阶段,系统讲解 BluetoothAdapter 的获取、启用、设备发现启动以及广播接收器的注册与撤销。掌握这些流程是构建蓝牙通信应用的基础,后续所有配对与数据传输操作都依赖本环节的正确实现。

3.1 核心原理

Android 蓝牙框架采用分层架构:上层的 BluetoothAdapter 代表本地蓝牙设备(即手机本身的蓝牙硬件),通过它才能控制蓝牙模块的开关、发起发现、获取绑定设备列表等。设备发现(Discovery)本质是让本机发送查询请求,并监听周围蓝牙设备发出的响应广播。这些广播由系统通过 Intent 发送,应用需要通过 BroadcastReceiver 接收并解析。

组件 职责
BluetoothAdapter 单例模式封装本地蓝牙硬件操作,提供 enable()、disable()、startDiscovery()、getBondedDevices() 等核心方法。
IntentFilter 声明应用关注的蓝牙广播类型,如 BluetoothDevice.ACTION_FOUND、BluetoothAdapter.ACTION_DISCOVERY_STARTED。
BroadcastReceiver 接收并处理广播,从中提取 BluetoothDevice 对象,获取设备名称、MAC 地址、RSSI 信号强度。

3.2 实现流程

标准设备发现流程分为 6 个步骤,每一步都必须检查状态并处理异常。

  1. 获取蓝牙适配器:调用 BluetoothAdapter.getDefaultAdapter(),若返回 null 表示设备不支持蓝牙,应提示用户。
  2. 检查并请求启用:若适配器未启用,调用 startActivityForResult() 弹出系统启用对话框,或直接调用 adapter.enable()(需要 BLUETOOTH_ADMIN 权限)。
  3. 注册广播接收器:创建 IntentFilter 添加 ACTION_FOUND、ACTION_DISCOVERY_FINISHED 等 Action,使用 registerReceiver() 注册。
  4. 取消正在进行的发现:调用 adapter.cancelDiscovery() 避免重复发现影响性能。
  5. 启动发现:调用 adapter.startDiscovery(),异步执行,发现过程约持续 12 秒。
  6. 在接收器中处理结果:重写 onReceive() 方法,将发现的设备存入列表或直接存储到 SharedPreferences。
注意: Android 10(API 29)及以上要求 ACCESS_FINE_LOCATION 权限才能扫描到蓝牙设备;Android 12 新增 BLUETOOTH_SCAN、BLUETOOTH_ADVERTISE、BLUETOOTH_CONNECT 等运行时权限,需适配。

3.3 核心 API 与代码示例

以下代码片段演示完整的初始化与发现流程,包含运行时权限检查(以 API 31 为例)。

// 1. 获取适配器
BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
if (bluetoothAdapter == null) {
    // 设备不支持蓝牙
    return;
}

// 2. 启用蓝牙(方式一:弹出系统对话框)
if (!bluetoothAdapter.isEnabled()) {
    Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
    startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
    // 在 onActivityResult 中继续后续步骤
}

// 3. 创建广播接收器
private final BroadcastReceiver discoveryReceiver = 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) 0);
            String deviceName = device.getName();
            String deviceAddress = device.getAddress();
            // 存储到列表或 SharedPreferences
        } else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(action)) {
            // 发现结束,可更新 UI
        }
    }
};

// 4. 注册接收器
IntentFilter filter = new IntentFilter();
filter.addAction(BluetoothDevice.ACTION_FOUND);
filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
registerReceiver(discoveryReceiver, filter);

// 5. 取消之前的发现并启动新发现
bluetoothAdapter.cancelDiscovery();
bluetoothAdapter.startDiscovery();
重要:必须在 onDestroy() 或不再需要时调用 unregisterReceiver(discoveryReceiver),否则会引发内存泄漏。同时,startDiscovery() 是耗电操作,建议在 Activity 可见时调用,并在离开界面时及时取消。

3.4 调试与常见错误

错误场景 可能原因 解决方案
adapter 为 null 设备无蓝牙硬件或模拟器未配置 检查设备兼容性,真机调试
无法发现任何设备 权限不足 / 蓝牙未启用 / 位置未开 确认已授予 BLUETOOTH、BLUETOOTH_ADMIN、ACCESS_FINE_LOCATION;在设置中开启位置服务
广播接收器未收到 ACTION_FOUND 未正确注册 / IntentFilter 缺少 Action / 注册在非 Activity Context 使用 Activity 实例注册,或确保 Application Context 已正确处理
发现中途停止 多次调用 startDiscovery() 导致冲突 始终先调用 cancelDiscovery() 再启动
Android 12+ 无设备出现 未请求运行时蓝牙权限 在代码中请求 BLUETOOTH_SCAN、BLUETOOTH_CONNECT,且目标 SDK 需适配

3.5 性能与稳定性优化

  • 避免频繁发现:一次发现过程耗电约 20~30 mA,连续调用会大幅缩短续航;建议在用户主动点击“扫描”时才启动,并设置超时自动停止(如 15 秒后调用 cancelDiscovery())。
  • 及时注销广播:在 onPause() 或 onStop() 中调用 unregisterReceiver,在 onResume() 中重新注册,避免 Activity 不可见时仍占用资源。
  • 缓存发现结果:对发现的设备按 MAC 地址去重,避免重复添加相同设备;可使用 HashMap<String, BluetoothDevice> 临时存储,发现结束后一次性写入 SharedPreferences。
  • 检查适配器状态:每次操作前调用 isEnabled() 确认蓝牙仍在开启状态,防止用户在使用过程中手动关闭蓝牙导致异常。

3.6 设备发现流程示意图

Bluetooth 设备发现完整流程 getDefaultAdapter() isEnabled() → 启用 registerReceiver() startDiscovery() onReceive() → 存储 提示:每步之间需检查权限与适配器状态;发现结束后务必调用 cancelDiscovery() 并注销接收器。

3.7 本章小结

蓝牙适配器初始化与设备发现是蓝牙通信的入口环节。开发者需牢记以下要点:

  • 必须检查 BluetoothAdapter 是否为 null;
  • 启用蓝牙前先检查 isEnabled(),优先使用系统对话框而非强制 enable();
  • 广播接收器的注册与注销必须成对出现;
  • Android 11+ 对蓝牙扫描权限要求严格,务必处理运行时权限回调;
  • 使用 getBondedDevices() 可获取已配对设备作为快速连接缓存。