Android 蓝牙设备连接广播详解-2026

Android 蓝牙设备连接广播详解-新

文章目录

  • [Android 蓝牙设备连接广播详解-新](#Android 蓝牙设备连接广播详解-新)
    • 一、前言
    • 二、蓝牙权限配置
      • [2.1 基础蓝牙权限](#2.1 基础蓝牙权限)
      • [2.2 Android 12 及以上版本权限](#2.2 Android 12 及以上版本权限)
    • 三、蓝牙连接状态相关广播
      • [3.1 主要广播 Action](#3.1 主要广播 Action)
      • [3.2 各广播的区别与使用场景(重要)](#3.2 各广播的区别与使用场景(重要))
        • [3.2.1 ACTION_ACL_CONNECTED / ACTION_ACL_DISCONNECTED](#3.2.1 ACTION_ACL_CONNECTED / ACTION_ACL_DISCONNECTED)
        • [3.2.2 BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED](#3.2.2 BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED)
        • [3.2.3 BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED(推荐)](#3.2.3 BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED(推荐))
        • [3.2.4 广播选择对比总结](#3.2.4 广播选择对比总结)
      • [3.3 ACL 连接广播详解](#3.3 ACL 连接广播详解)
    • 四、广播接收器实现
      • [4.1 静态注册方式](#4.1 静态注册方式)
      • [4.2 动态注册方式](#4.2 动态注册方式)
      • [4.3 广播接收器完整实现](#4.3 广播接收器完整实现)
    • 五、获取广播中的额外数据
      • [5.1 常用 Extra 字段](#5.1 常用 Extra 字段)
      • [5.2 获取设备信息示例](#5.2 获取设备信息示例)
    • [六、经典蓝牙与 BLE 的区别](#六、经典蓝牙与 BLE 的区别)
      • [6.1 经典蓝牙连接广播](#6.1 经典蓝牙连接广播)
      • [6.2 BLE 连接状态监听](#6.2 BLE 连接状态监听)
    • 七、注意事项
      • [7.1 Android 版本适配](#7.1 Android 版本适配)
      • [7.2 动态注册优先](#7.2 动态注册优先)
      • [7.3 内存泄漏问题](#7.3 内存泄漏问题)
      • [7.4 广播接收时机](#7.4 广播接收时机)
    • [八、完整工具类封装(推荐使用 A2DP 广播)](#八、完整工具类封装(推荐使用 A2DP 广播))
      • [8.1 使用示例](#8.1 使用示例)
    • 九、常见问题与解决方案
      • [9.1 为什么 ACTION_ACL_CONNECTED 不准确?](#9.1 为什么 ACTION_ACL_CONNECTED 不准确?)
      • [9.2 为什么多设备连接时第二个设备收不到广播?](#9.2 为什么多设备连接时第二个设备收不到广播?)
      • [9.3 如何判断蓝牙设备的连接类型?](#9.3 如何判断蓝牙设备的连接类型?)
    • [十、蓝牙 Profile 详解:A2DP、Headset、HID 的区别](#十、蓝牙 Profile 详解:A2DP、Headset、HID 的区别)
      • [10.1 BluetoothA2dp(Advanced Audio Distribution Profile)](#10.1 BluetoothA2dp(Advanced Audio Distribution Profile))
        • [10.1.1 简介](#10.1.1 简介)
        • [10.1.2 核心特点](#10.1.2 核心特点)
        • [10.1.3 应用场景](#10.1.3 应用场景)
        • [10.1.4 代码示例](#10.1.4 代码示例)
      • [10.2 BluetoothHeadset(Headset Profile / Hands-Free Profile)](#10.2 BluetoothHeadset(Headset Profile / Hands-Free Profile))
        • [10.2.1 简介](#10.2.1 简介)
        • [10.2.2 核心特点](#10.2.2 核心特点)
        • [10.2.3 A2DP 与 Headset 的区别](#10.2.3 A2DP 与 Headset 的区别)
        • [10.2.4 应用场景](#10.2.4 应用场景)
        • [10.2.5 代码示例](#10.2.5 代码示例)
      • [10.3 BluetoothHidDevice(Human Interface Device Profile)](#10.3 BluetoothHidDevice(Human Interface Device Profile))
        • [10.3.1 简介](#10.3.1 简介)
        • [10.3.2 核心特点](#10.3.2 核心特点)
        • [10.3.3 应用场景](#10.3.3 应用场景)
        • [10.3.4 代码示例](#10.3.4 代码示例)
      • [10.4 三种 Profile 对比总结](#10.4 三种 Profile 对比总结)
        • [10.4.1 功能对比表](#10.4.1 功能对比表)
        • [10.4.2 设备连接流程对比](#10.4.2 设备连接流程对比)
        • [10.4.3 选择建议](#10.4.3 选择建议)
      • [10.5 综合使用示例:监听所有类型的蓝牙设备](#10.5 综合使用示例:监听所有类型的蓝牙设备)
    • 十一、总结
    • 参考资料

一、前言

在 Android 蓝牙开发过程中,监听蓝牙设备的连接状态变化是一项非常常见的需求。

无论是开发蓝牙音频设备、健康监测设备还是智能家居应用,都需要实时获取蓝牙设备的连接状态。

Android 系统通过广播机制将蓝牙设备的各种状态变化通知给应用程序,

蓝牙开关和发现那些广播这里就不介绍了,主要介绍蓝牙状态的广播;

问题:多个蓝牙设备连接的时候,有些设备断开连接就没有状态广播了,这个需要如何解决?

如果不想看过程,直接后面总结的关键点,就知道答案了。

本文将详细介绍 Android 蓝牙设备连接相关的广播及其使用方法。


二、蓝牙权限配置

在监听蓝牙广播之前,首先需要在 AndroidManifest.xml 文件中声明相应的权限。

2.1 基础蓝牙权限

xml 复制代码
<!-- 基础蓝牙权限 -->
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />

2.2 Android 12 及以上版本权限

从 Android 12 (API 31) 开始,蓝牙权限进行了细化,需要申请新的权限:

xml 复制代码
<!-- Android 12+ 蓝牙权限 -->
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />

注意 :如果应用不需要物理定位功能,建议添加 android:usesPermissionFlags="neverForLocation" 属性,以便在应用商店中避免不必要的隐私声明。


三、蓝牙连接状态相关广播

3.1 主要广播 Action

以下是蓝牙设备连接状态相关的核心广播:

广播 Action 说明
BluetoothDevice.ACTION_ACL_CONNECTED 蓝牙设备 ACL 链路已连接
BluetoothDevice.ACTION_ACL_DISCONNECTED 蓝牙设备 ACL 链路已断开连接
BluetoothDevice.ACTION_ACL_DISCONNECT_REQUESTED 蓝牙设备正在请求断开连接
BluetoothAdapter.ACTION_STATE_CHANGED 蓝牙适配器开关状态改变
BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED 蓝牙适配器整体连接状态改变
BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED A2DP 音频设备连接状态改变
BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED 蓝牙耳机连接状态改变
BluetoothHidDevice.ACTION_CONNECTION_STATE_CHANGED HID 设备连接状态改变

3.2 各广播的区别与使用场景(重要)

在实际开发中,正确选择广播类型非常重要,不同的广播有不同的触发时机和适用场景:

3.2.1 ACTION_ACL_CONNECTED / ACTION_ACL_DISCONNECTED

触发时机:设备完成配对后建立 ACL 物理链路时触发。

重要特点

  • 该广播表示蓝牙底层物理链路的连接状态,不是真正的音频连接状态
  • 设备配对后就会触发 ACTION_ACL_CONNECTED,此时音频通道可能还未建立
  • 如果使用此广播来刷新蓝牙音箱的连接状态,会导致状态不准确

适用场景:需要监听蓝牙设备物理链路连接状态的场景。

java 复制代码
// ⚠️ 注意:不建议用此广播判断蓝牙音箱是否真正连接成功
if (BluetoothDevice.ACTION_ACL_CONNECTED.equals(action)) {
    // 此时设备可能只是配对连接,音频通道可能还未建立
    // 对于蓝牙音箱场景,这并不是最佳的连接状态判断方式
}
3.2.2 BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED

触发时机:蓝牙适配器的整体连接状态变化时触发。

重要特点

  • 表示整个蓝牙适配器的连接状态,而非具体某个设备
  • 多设备连接场景下存在问题 :当第一个蓝牙音箱已连接,第二个蓝牙音箱连接时,此广播不会触发
  • 只能监听是否有设备连接,无法区分具体是哪个设备

适用场景:只需知道蓝牙是否有设备连接的场景,不适合多设备管理。

java 复制代码
// ⚠️ 注意:多设备连接场景下,第二个设备连接时不会收到此广播
if (BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED.equals(action)) {
    int state = intent.getIntExtra(BluetoothAdapter.EXTRA_CONNECTION_STATE, 
                                    BluetoothAdapter.ERROR);
    // 只能判断整体是否有设备连接,无法区分具体设备
    // 第一个设备连接后,第二个设备连接不会触发此广播
}
3.2.3 BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED(推荐)

触发时机:A2DP 音频设备(如蓝牙音箱、蓝牙耳机)的音频连接状态变化时触发。

重要特点

  • 推荐使用此广播监听蓝牙音箱/耳机的连接状态
  • 能够正确接收多个蓝牙音箱同时连接时的状态变化
  • 每个设备的连接和断开都能准确接收到
  • 需要获取 BluetoothA2dp 服务实例才能正常工作

适用场景:蓝牙音箱、蓝牙耳机等 A2DP 音频设备的连接状态监听。

java 复制代码
// ✅ 推荐:使用 A2DP 广播监听蓝牙音箱连接状态
public static final String ACTION_A2DP_CONNECTION_STATE_CHANGED = 
    "android.bluetooth.a2dp.profile.action.CONNECTION_STATE_CHANGED";

// 注册广播时需要添加此 Action
filter.addAction(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED);

// 处理 A2DP 连接状态变化
if (BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED.equals(action)) {
    BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
    int state = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, 
                                    BluetoothProfile.STATE_DISCONNECTED);
    int prevState = intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE,
                                        BluetoothProfile.STATE_DISCONNECTED);
    
    if (state == BluetoothProfile.STATE_CONNECTED) {
        // 音频设备真正连接成功,可以播放音频
        Log.d(TAG, "A2DP 设备已连接: " + device.getName());
    } else if (state == BluetoothProfile.STATE_DISCONNECTED) {
        // 音频设备断开连接
        Log.d(TAG, "A2DP 设备已断开: " + device.getName());
    }
}
3.2.4 广播选择对比总结
广播类型 多设备支持 音频连接准确性 推荐场景
ACTION_ACL_CONNECTED ✅ 支持 ❌ 不准确(配对即触发) 物理链路监听
ACTION_CONNECTION_STATE_CHANGED (Adapter) ❌ 不支持 ⚠️ 有限支持 单设备简单场景
ACTION_CONNECTION_STATE_CHANGED (A2DP) ✅ 支持 ✅ 准确 蓝牙音箱/耳机(推荐)
ACTION_CONNECTION_STATE_CHANGED (Headset) ✅ 支持 ✅ 准确 蓝牙通话耳机

3.3 ACL 连接广播详解

ACL(Asynchronous Connection-Less)是蓝牙底层连接,表示蓝牙设备之间的物理链路连接状态。

.

ACTION_ACL_CONNECTED

当蓝牙设备成功建立 ACL 连接时,系统会发送此广播。

java 复制代码
// 广播 Action
public static final String ACTION_ACL_CONNECTED = "android.bluetooth.device.action.ACL_CONNECTED";
ACTION_ACL_DISCONNECTED

当蓝牙设备 ACL 连接断开时,系统会发送此广播。

java 复制代码
// 广播 Action
public static final String ACTION_ACL_DISCONNECTED = "android.bluetooth.device.action.ACL_DISCONNECTED";

四、广播接收器实现

4.1 静态注册方式

AndroidManifest.xml 中静态注册广播接收器:

xml 复制代码
<receiver
    android:name=".receiver.BluetoothStateReceiver"
    android:exported="true">
    <intent-filter>
        <action android:name="android.bluetooth.device.action.ACL_CONNECTED" />
        <action android:name="android.bluetooth.device.action.ACL_DISCONNECTED" />
        <action android:name="android.bluetooth.adapter.action.STATE_CHANGED" />
        <action android:name="android.bluetooth.adapter.action.CONNECTION_STATE_CHANGED" />
        <!-- A2DP 音频设备连接状态广播(推荐用于蓝牙音箱) -->
        <action android:name="android.bluetooth.a2dp.profile.action.CONNECTION_STATE_CHANGED" />
        <!-- 蓝牙耳机连接状态广播 -->
        <action android:name="android.bluetooth.headset.profile.action.CONNECTION_STATE_CHANGED" />
    </intent-filter>
</receiver>

4.2 动态注册方式

在代码中动态注册广播接收器(推荐):

java 复制代码
public class BluetoothManager {
    
    private BluetoothStateReceiver mReceiver;
    private Context mContext;
    
    /**
     * 注册蓝牙状态广播接收器
     */
    public void registerReceiver() {
        if (mReceiver == null) {
            mReceiver = new BluetoothStateReceiver();
        }
        
        IntentFilter filter = new IntentFilter();
        // ACL 连接广播
        filter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED);
        filter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED);
        // 蓝牙适配器状态广播
        filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
        filter.addAction(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED);
        // A2DP 音频设备连接状态广播(推荐用于蓝牙音箱)
        filter.addAction(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED);
        // 蓝牙耳机连接状态广播
        filter.addAction(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
        
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
            mContext.registerReceiver(mReceiver, filter, Context.RECEIVER_NOT_EXPORTED);
        } else {
            mContext.registerReceiver(mReceiver, filter);
        }
    }
    
    /**
     * 注销广播接收器
     */
    public void unregisterReceiver() {
        if (mReceiver != null) {
            mContext.unregisterReceiver(mReceiver);
            mReceiver = null;
        }
    }
}

4.3 广播接收器完整实现

java 复制代码
public class BluetoothStateReceiver extends BroadcastReceiver {
    
    private static final String TAG = "BluetoothStateReceiver";
    
    @Override
    public void onReceive(Context context, Intent intent) {
        if (intent == null || intent.getAction() == null) {
            return;
        }
        
        String action = intent.getAction();
        Log.d(TAG, "Received action: " + action);
        
        if (BluetoothDevice.ACTION_ACL_CONNECTED.equals(action)) {
            handleAclConnected(intent);
            
        } else if (BluetoothDevice.ACTION_ACL_DISCONNECTED.equals(action)) {
            handleAclDisconnected(intent);
            
        } else if (BluetoothAdapter.ACTION_STATE_CHANGED.equals(action)) {
            handleAdapterStateChanged(intent);
            
        } else if (BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED.equals(action)) {
            handleAdapterConnectionStateChanged(intent);
            
        } else if (BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED.equals(action)) {
            handleA2dpConnectionStateChanged(intent);
            
        } else if (BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED.equals(action)) {
            handleHeadsetConnectionStateChanged(intent);
        }
    }
    
    /**
     * 处理 ACL 连接成功
     * 注意:此广播在设备配对后就会触发,不代表音频通道已建立
     */
    private void handleAclConnected(Intent intent) {
        BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
        if (device != null) {
            Log.d(TAG, "ACL 链路已连接: " + device.getName() + " - " + device.getAddress());
            Log.d(TAG, "注意:此状态不代表音频通道已建立,建议使用 A2DP 广播判断音频连接");
        }
    }
    
    /**
     * 处理 ACL 断开连接
     */
    private void handleAclDisconnected(Intent intent) {
        BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
        if (device != null) {
            Log.d(TAG, "ACL 链路已断开: " + device.getName() + " - " + device.getAddress());
        }
    }
    
    /**
     * 处理蓝牙适配器开关状态变化
     */
    private void handleAdapterStateChanged(Intent intent) {
        int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR);
        
        switch (state) {
            case BluetoothAdapter.STATE_OFF:
                Log.d(TAG, "蓝牙已关闭");
                break;
            case BluetoothAdapter.STATE_ON:
                Log.d(TAG, "蓝牙已开启");
                break;
            case BluetoothAdapter.STATE_TURNING_OFF:
                Log.d(TAG, "蓝牙正在关闭");
                break;
            case BluetoothAdapter.STATE_TURNING_ON:
                Log.d(TAG, "蓝牙正在开启");
                break;
        }
    }
    
    /**
     * 处理蓝牙适配器整体连接状态变化
     * 注意:多设备连接时,第二个设备连接不会触发此广播
     */
    private void handleAdapterConnectionStateChanged(Intent intent) {
        int state = intent.getIntExtra(BluetoothAdapter.EXTRA_CONNECTION_STATE, 
                                        BluetoothAdapter.ERROR);
        int prevState = intent.getIntExtra(BluetoothAdapter.EXTRA_PREVIOUS_CONNECTION_STATE,
                                           BluetoothAdapter.ERROR);
        
        Log.d(TAG, "适配器连接状态变化: " + prevState + " -> " + state);
        Log.d(TAG, "注意:多设备场景下,第二个设备连接时不会触发此广播");
        
        switch (state) {
            case BluetoothAdapter.STATE_CONNECTED:
                Log.d(TAG, "有蓝牙设备已连接");
                break;
            case BluetoothAdapter.STATE_DISCONNECTED:
                Log.d(TAG, "所有蓝牙设备已断开");
                break;
            case BluetoothAdapter.STATE_CONNECTING:
                Log.d(TAG, "蓝牙正在连接");
                break;
            case BluetoothAdapter.STATE_DISCONNECTING:
                Log.d(TAG, "蓝牙正在断开");
                break;
        }
    }
    
    /**
     * 处理 A2DP 音频设备连接状态变化(推荐用于蓝牙音箱)
     * 特点:支持多设备连接,状态准确
     */
    private void handleA2dpConnectionStateChanged(Intent intent) {
        BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
        int state = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, 
                                        BluetoothProfile.STATE_DISCONNECTED);
        int prevState = intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE,
                                           BluetoothProfile.STATE_DISCONNECTED);
        
        if (device != null) {
            Log.d(TAG, "A2DP 设备连接状态变化: " + device.getName());
            Log.d(TAG, "状态: " + prevState + " -> " + state);
            
            switch (state) {
                case BluetoothProfile.STATE_CONNECTED:
                    Log.d(TAG, "A2DP 设备已连接(音频通道已建立)");
                    // 发送连接成功通知
                    EventBus.getDefault().post(new BluetoothConnectEvent(device, true));
                    break;
                    
                case BluetoothProfile.STATE_DISCONNECTED:
                    Log.d(TAG, "A2DP 设备已断开");
                    // 发送断开连接通知
                    EventBus.getDefault().post(new BluetoothConnectEvent(device, false));
                    break;
                    
                case BluetoothProfile.STATE_CONNECTING:
                    Log.d(TAG, "A2DP 设备正在连接");
                    break;
                    
                case BluetoothProfile.STATE_DISCONNECTING:
                    Log.d(TAG, "A2DP 设备正在断开");
                    break;
            }
        }
    }
    
    /**
     * 处理蓝牙耳机连接状态变化
     */
    private void handleHeadsetConnectionStateChanged(Intent intent) {
        BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
        int state = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, 
                                        BluetoothProfile.STATE_DISCONNECTED);
        
        if (device != null) {
            Log.d(TAG, "Headset 设备连接状态: " + state);
            
            if (state == BluetoothProfile.STATE_CONNECTED) {
                Log.d(TAG, "蓝牙耳机已连接");
            } else if (state == BluetoothProfile.STATE_DISCONNECTED) {
                Log.d(TAG, "蓝牙耳机已断开");
            }
        }
    }
}

五、获取广播中的额外数据

蓝牙广播中包含了丰富的额外数据,可以通过 Intent 的 Extra 字段获取。

5.1 常用 Extra 字段

Extra 字段 说明 适用广播
BluetoothDevice.EXTRA_DEVICE 蓝牙设备对象 所有蓝牙广播
BluetoothAdapter.EXTRA_STATE 当前状态 Adapter 状态广播
BluetoothAdapter.EXTRA_PREVIOUS_STATE 之前的状态 Adapter 状态广播
BluetoothAdapter.EXTRA_CONNECTION_STATE 当前连接状态 Adapter 连接广播
BluetoothAdapter.EXTRA_PREVIOUS_CONNECTION_STATE 之前的连接状态 Adapter 连接广播
BluetoothProfile.EXTRA_STATE 当前 Profile 状态 A2DP/Headset 广播
BluetoothProfile.EXTRA_PREVIOUS_STATE 之前的 Profile 状态 A2DP/Headset 广播

具体字段新可以看源码中的定义。

5.2 获取设备信息示例

java 复制代码
/**
 * 从 Intent 中获取蓝牙设备详细信息
 */
private void parseDeviceInfo(Intent intent) {
    // 获取蓝牙设备对象
    BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
    
    if (device != null) {
        String name = device.getName();           // 设备名称
        String address = device.getAddress();     // MAC 地址
        int bondState = device.getBondState();    // 配对状态
        int type = device.getType();              // 设备类型
        
        Log.d(TAG, "设备名称: " + name);
        Log.d(TAG, "设备地址: " + address);
        Log.d(TAG, "配对状态: " + bondStateToString(bondState));
        Log.d(TAG, "设备类型: " + deviceTypeToString(type));
    }
}

/**
 * 配对状态转字符串
 */
private String bondStateToString(int bondState) {
    switch (bondState) {
        case BluetoothDevice.BOND_NONE:
            return "未配对";
        case BluetoothDevice.BOND_BONDING:
            return "正在配对";
        case BluetoothDevice.BOND_BONDED:
            return "已配对";
        default:
            return "未知";
    }
}

/**
 * 设备类型转字符串
 */
private String deviceTypeToString(int type) {
    switch (type) {
        case BluetoothDevice.DEVICE_TYPE_CLASSIC:
            return "经典蓝牙";
        case BluetoothDevice.DEVICE_TYPE_LE:
            return "低功耗蓝牙(BLE)";
        case BluetoothDevice.DEVICE_TYPE_DUAL:
            return "双模蓝牙";
        case BluetoothDevice.DEVICE_TYPE_UNKNOWN:
        default:
            return "未知类型";
    }
}

六、经典蓝牙与 BLE 的区别

在实际开发中,经典蓝牙和低功耗蓝牙(BLE)的广播处理有所区别。

6.1 经典蓝牙连接广播

经典蓝牙使用 ACL 连接广播:

  • ACTION_ACL_CONNECTED
  • ACTION_ACL_DISCONNECTED

6.2 BLE 连接状态监听

对于 BLE 设备,需要使用 BluetoothGatt 回调来监听连接状态:

java 复制代码
private BluetoothGattCallback mGattCallback = new BluetoothGattCallback() {
    
    @Override
    public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
        super.onConnectionStateChange(gatt, status, newState);
        
        if (newState == BluetoothProfile.STATE_CONNECTED) {
            Log.d(TAG, "BLE 设备已连接");
            // 发现服务
            gatt.discoverServices();
            
        } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
            Log.d(TAG, "BLE 设备已断开");
            gatt.close();
        }
    }
    
    @Override
    public void onServicesDiscovered(BluetoothGatt gatt, int status) {
        super.onServicesDiscovered(gatt, status);
        
        if (status == BluetoothGatt.GATT_SUCCESS) {
            Log.d(TAG, "服务发现成功");
            // 处理服务发现结果
        }
    }
};

七、注意事项

7.1 Android 版本适配

不同 Android 版本对蓝牙权限和广播的处理有所差异,需要注意以下要点:

Android 版本 注意事项
Android 6.0+ 需要动态申请位置权限才能扫描蓝牙设备
Android 10+ 需要申请后台位置权限
Android 12+ 需要申请新的蓝牙权限(BLUETOOTH_SCAN/CONNECT)
Android 13+ 建议使用 RECEIVER_NOT_EXPORTED 注册广播

7.2 动态注册优先

建议使用动态注册方式,原因如下:

  1. 静态注册在应用被系统杀死后可能无法接收到广播
  2. Android 8.0+ 对静态注册广播有诸多限制
  3. 动态注册更加灵活,可以控制注册和注销时机

7.3 内存泄漏问题

在使用动态注册时,务必在合适的生命周期注销广播接收器:

java 复制代码
@Override
protected void onDestroy() {
    super.onDestroy();
    // 注销广播接收器,防止内存泄漏
    if (mBluetoothReceiver != null) {
        unregisterReceiver(mBluetoothReceiver);
    }
}

7.4 广播接收时机

部分广播可能需要在应用运行时才能接收,建议结合以下方式:

  • 使用前台服务保持应用活跃
  • 使用 WorkManager 定期检查蓝牙状态
  • 结合 JobScheduler 进行状态同步

八、完整工具类封装(推荐使用 A2DP 广播)

以下是封装好的蓝牙状态监听工具类,推荐使用 A2DP 广播来监听蓝牙音箱/耳机的连接状态:

java 复制代码
/**
 * 蓝牙状态监听工具类
 * 推荐使用 A2DP 广播监听蓝牙音箱/耳机的连接状态
 */
public class BluetoothStateHelper {
    
    private static final String TAG = "BluetoothStateHelper";
    
    private Context mContext;
    private BluetoothStateReceiver mReceiver;
    private OnBluetoothStateListener mListener;
    
    /**
     * 蓝牙状态监听接口
     */
    public interface OnBluetoothStateListener {
        /**
         * A2DP 设备连接成功(推荐使用)
         */
        void onA2dpDeviceConnected(BluetoothDevice device);
        
        /**
         * A2DP 设备断开连接
         */
        void onA2dpDeviceDisconnected(BluetoothDevice device);
        
        /**
         * 蓝牙适配器开关状态变化
         */
        void onAdapterStateChanged(int state);
    }
    
    public BluetoothStateHelper(Context context) {
        this.mContext = context.getApplicationContext();
    }
    
    /**
     * 设置监听器
     */
    public void setOnBluetoothStateListener(OnBluetoothStateListener listener) {
        this.mListener = listener;
    }
    
    /**
     * 开始监听
     */
    public void startListen() {
        if (mReceiver == null) {
            mReceiver = new BluetoothStateReceiver();
            
            IntentFilter filter = new IntentFilter();
            // 蓝牙适配器状态广播
            filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
            // A2DP 音频设备连接状态广播(推荐)
            filter.addAction(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED);
            // 蓝牙耳机连接状态广播
            filter.addAction(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
            // ACL 连接广播(可选,用于底层链路监听)
            filter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED);
            filter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED);
            
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
                mContext.registerReceiver(mReceiver, filter, Context.RECEIVER_NOT_EXPORTED);
            } else {
                mContext.registerReceiver(mReceiver, filter);
            }
        }
    }
    
    /**
     * 停止监听
     */
    public void stopListen() {
        if (mReceiver != null) {
            mContext.unregisterReceiver(mReceiver);
            mReceiver = null;
        }
    }
    
    /**
     * 内部广播接收器
     */
    private class BluetoothStateReceiver extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {
            if (intent == null || intent.getAction() == null) {
                return;
            }
            
            String action = intent.getAction();
            BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
            
            // A2DP 音频设备连接状态变化(推荐用于蓝牙音箱)
            if (BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED.equals(action)) {
                int state = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, 
                                                BluetoothProfile.STATE_DISCONNECTED);
                
                if (state == BluetoothProfile.STATE_CONNECTED) {
                    Log.d(TAG, "A2DP 设备已连接: " + getDeviceInfo(device));
                    if (mListener != null && device != null) {
                        mListener.onA2dpDeviceConnected(device);
                    }
                } else if (state == BluetoothProfile.STATE_DISCONNECTED) {
                    Log.d(TAG, "A2DP 设备已断开: " + getDeviceInfo(device));
                    if (mListener != null && device != null) {
                        mListener.onA2dpDeviceDisconnected(device);
                    }
                }
                
            // 蓝牙适配器开关状态变化
            } else if (BluetoothAdapter.ACTION_STATE_CHANGED.equals(action)) {
                int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, 
                                               BluetoothAdapter.ERROR);
                Log.d(TAG, "蓝牙适配器状态: " + state);
                if (mListener != null) {
                    mListener.onAdapterStateChanged(state);
                }
                
            // ACL 链路连接(仅供参考,不建议用于判断音频连接状态)
            } else if (BluetoothDevice.ACTION_ACL_CONNECTED.equals(action)) {
                Log.d(TAG, "ACL 链路已连接: " + getDeviceInfo(device) + 
                      "(注意:此状态不代表音频已连接)");
                
            // ACL 链路断开
            } else if (BluetoothDevice.ACTION_ACL_DISCONNECTED.equals(action)) {
                Log.d(TAG, "ACL 链路已断开: " + getDeviceInfo(device));
            }
        }
    }
    
    /**
     * 获取设备信息字符串
     */
    private String getDeviceInfo(BluetoothDevice device) {
        if (device == null) {
            return "null";
        }
        return device.getName() + " [" + device.getAddress() + "]";
    }
}

8.1 使用示例

java 复制代码
public class MainActivity extends AppCompatActivity {
    
    private BluetoothStateHelper mBluetoothHelper;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        // 初始化蓝牙状态监听
        mBluetoothHelper = new BluetoothStateHelper(this);
        mBluetoothHelper.setOnBluetoothStateListener(new BluetoothStateHelper.OnBluetoothStateListener() {
            @Override
            public void onA2dpDeviceConnected(BluetoothDevice device) {
                // 蓝牙音箱真正连接成功,可以开始播放音频
                runOnUiThread(() -> {
                    Toast.makeText(MainActivity.this, 
                                   "蓝牙音箱已连接: " + device.getName(), 
                                   Toast.LENGTH_SHORT).show();
                    updateUI(device, true);
                });
            }
            
            @Override
            public void onA2dpDeviceDisconnected(BluetoothDevice device) {
                // 蓝牙音箱断开连接
                runOnUiThread(() -> {
                    Toast.makeText(MainActivity.this, 
                                   "蓝牙音箱已断开: " + device.getName(), 
                                   Toast.LENGTH_SHORT).show();
                    updateUI(device, false);
                });
            }
            
            @Override
            public void onAdapterStateChanged(int state) {
                // 蓝牙开关状态变化
                if (state == BluetoothAdapter.STATE_OFF) {
                    // 蓝牙已关闭,清空设备列表
                    runOnUiThread(() -> clearDeviceList());
                }
            }
        });
        
        // 开始监听
        mBluetoothHelper.startListen();
    }
    
    @Override
    protected void onDestroy() {
        super.onDestroy();
        // 停止监听,防止内存泄漏
        if (mBluetoothHelper != null) {
            mBluetoothHelper.stopListen();
        }
    }
    
    private void updateUI(BluetoothDevice device, boolean connected) {
        // 更新 UI 显示
    }
    
    private void clearDeviceList() {
        // 清空设备列表
    }
}

九、常见问题与解决方案

9.1 为什么 ACTION_ACL_CONNECTED 不准确?

问题 :使用 ACTION_ACL_CONNECTED 判断蓝牙音箱连接状态,发现有时已经收到广播,但音频还不能播放。

原因ACTION_ACL_CONNECTED 只表示蓝牙底层的 ACL 物理链路已连接,这是在设备配对成功后就会触发的。此时音频通道(A2DP)可能还未完全建立。

解决方案 :使用 BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED 广播,它在音频通道真正建立连接时才会触发。

9.2 为什么多设备连接时第二个设备收不到广播?

问题:第一个蓝牙音箱连接正常,第二个蓝牙音箱连接时收不到任何广播。

原因BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED 表示的是蓝牙适配器的整体连接状态。当第一个设备连接后,适配器状态已经是 "已连接",第二个设备连接时状态没有变化,所以不会触发广播。

解决方案 :使用 BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED 广播,它可以正确接收每个 A2DP 设备的连接状态变化。

9.3 如何判断蓝牙设备的连接类型?

不同类型的蓝牙设备使用不同的 Profile,可以通过注册不同的广播来监听:

设备类型 Profile 推荐广播
蓝牙音箱 A2DP BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED
蓝牙耳机(通话) HFP/HSP BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED
蓝牙键盘鼠标 HID BluetoothHidDevice.ACTION_CONNECTION_STATE_CHANGED
健康设备 HDP BluetoothHealth.ACTION_CONNECTION_STATE_CHANGED

十、蓝牙 Profile 详解:A2DP、Headset、HID 的区别

在 Android 蓝牙开发中,不同的蓝牙设备使用不同的 Profile(配置文件)进行通信。理解这些 Profile 的区别对于正确监听设备连接状态至关重要。

10.1 BluetoothA2dp(Advanced Audio Distribution Profile)

10.1.1 简介

A2DP(高级音频分发配置文件)是用于传输高质量立体声音频流的蓝牙 Profile。

10.1.2 核心特点
特性 说明
全称 Advanced Audio Distribution Profile
用途 传输高质量立体声音频
音频质量 高质量(支持 AAC、SBC、aptX、LDAC 等编码)
传输方向 单向(从手机到播放设备)
典型设备 蓝牙音箱、蓝牙耳机(听歌模式)
10.1.3 应用场景
复制代码
┌─────────────────────────────────────────────────────────┐
│                    A2DP 应用场景                         │
├─────────────────────────────────────────────────────────┤
│  手机 ──────A2DP──────► 蓝牙音箱(播放音乐)              │
│  手机 ──────A2DP──────► 蓝牙耳机(听歌、看视频)          │
│  电脑 ──────A2DP──────► 蓝牙音响(无线播放)              │
└─────────────────────────────────────────────────────────┘
10.1.4 代码示例
java 复制代码
/**
 * 监听 A2DP 设备连接状态
 * 适用设备:蓝牙音箱、蓝牙耳机(听歌模式)
 */
public class A2dpStateHelper {
    
    private Context mContext;
    private A2dpReceiver mReceiver;
    private BluetoothA2dp mBluetoothA2dp;
    
    /**
     * 注册 A2DP 服务监听
     */
    public void registerA2dpService() {
        BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
        if (adapter == null) {
            return;
        }
        
        // 获取 A2DP Profile 代理
        adapter.getProfileProxy(mContext, new BluetoothProfile.ServiceListener() {
            @Override
            public void onServiceConnected(int profile, BluetoothProfile proxy) {
                if (profile == BluetoothProfile.A2DP) {
                    mBluetoothA2dp = (BluetoothA2dp) proxy;
                    Log.d(TAG, "A2DP 服务已连接");
                    
                    // 获取已连接的 A2DP 设备列表
                    List<BluetoothDevice> connectedDevices = mBluetoothA2dp.getConnectedDevices();
                    for (BluetoothDevice device : connectedDevices) {
                        Log.d(TAG, "已连接的 A2DP 设备: " + device.getName());
                    }
                }
            }
            
            @Override
            public void onServiceDisconnected(int profile) {
                if (profile == BluetoothProfile.A2DP) {
                    mBluetoothA2dp = null;
                    Log.d(TAG, "A2DP 服务已断开");
                }
            }
        }, BluetoothProfile.A2DP);
    }
    
    /**
     * 注册 A2DP 连接状态广播
     */
    public void registerReceiver() {
        mReceiver = new A2dpReceiver();
        IntentFilter filter = new IntentFilter(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED);
        
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
            mContext.registerReceiver(mReceiver, filter, Context.RECEIVER_NOT_EXPORTED);
        } else {
            mContext.registerReceiver(mReceiver, filter);
        }
    }
    
    /**
     * A2DP 连接状态广播接收器
     */
    private class A2dpReceiver extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {
            BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
            int state = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, 
                                            BluetoothProfile.STATE_DISCONNECTED);
            
            switch (state) {
                case BluetoothProfile.STATE_CONNECTED:
                    Log.d(TAG, "A2DP 设备已连接: " + device.getName());
                    // 此时可以开始播放音乐
                    break;
                case BluetoothProfile.STATE_DISCONNECTED:
                    Log.d(TAG, "A2DP 设备已断开: " + device.getName());
                    // 暂停音乐播放
                    break;
            }
        }
    }
}

10.2 BluetoothHeadset(Headset Profile / Hands-Free Profile)

10.2.1 简介

Headset Profile 包含 HSP(Headset Profile)和 HFP(Hands-Free Profile),主要用于语音通话场景。

10.2.2 核心特点
特性 说明
全称 Headset Profile / Hands-Free Profile
用途 语音通话、电话音频传输
音频质量 较低(主要用于语音,带宽约 8kHz)
传输方向 双向(双方都可以说话和听)
典型设备 蓝牙耳机(通话模式)、车载蓝牙、蓝牙耳麦
10.2.3 A2DP 与 Headset 的区别
对比项 A2DP Headset (HFP/HSP)
主要用途 音乐播放、媒体音频 电话通话、语音输入
音频质量 高质量立体声 单声道语音质量
传输方向 单向(源→接收端) 双向(双方通话)
延迟要求 相对宽松 要求低延迟
典型设备 蓝牙音箱 车载蓝牙、蓝牙耳麦
10.2.4 应用场景
复制代码
┌─────────────────────────────────────────────────────────┐
│                   Headset 应用场景                       │
├─────────────────────────────────────────────────────────┤
│  手机 ←──Headset──► 蓝牙耳机(接听电话)                 │
│  手机 ←──Headset──► 车载蓝牙(免提通话)                 │
│  手机 ←──Headset──► 蓝牙耳麦(语音通话、语音助手)       │
└─────────────────────────────────────────────────────────┘
10.2.5 代码示例
java 复制代码
/**
 * 监听 Headset 设备连接状态
 * 适用设备:蓝牙耳机(通话模式)、车载蓝牙、蓝牙耳麦
 */
public class HeadsetStateHelper {
    
    private Context mContext;
    private HeadsetReceiver mReceiver;
    private BluetoothHeadset mBluetoothHeadset;
    
    /**
     * 注册 Headset 服务监听
     */
    public void registerHeadsetService() {
        BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
        if (adapter == null) {
            return;
        }
        
        // 获取 Headset Profile 代理
        adapter.getProfileProxy(mContext, new BluetoothProfile.ServiceListener() {
            @Override
            public void onServiceConnected(int profile, BluetoothProfile proxy) {
                if (profile == BluetoothProfile.HEADSET) {
                    mBluetoothHeadset = (BluetoothHeadset) proxy;
                    Log.d(TAG, "Headset 服务已连接");
                    
                    // 获取已连接的 Headset 设备列表
                    List<BluetoothDevice> connectedDevices = mBluetoothHeadset.getConnectedDevices();
                    for (BluetoothDevice device : connectedDevices) {
                        Log.d(TAG, "已连接的 Headset 设备: " + device.getName());
                        
                        // 检查音频连接状态
                        int audioState = mBluetoothHeadset.isAudioConnected(device) ? 1 : 0;
                        Log.d(TAG, "音频连接状态: " + audioState);
                    }
                }
            }
            
            @Override
            public void onServiceDisconnected(int profile) {
                if (profile == BluetoothProfile.HEADSET) {
                    mBluetoothHeadset = null;
                    Log.d(TAG, "Headset 服务已断开");
                }
            }
        }, BluetoothProfile.HEADSET);
    }
    
    /**
     * 注册 Headset 连接状态广播
     */
    public void registerReceiver() {
        mReceiver = new HeadsetReceiver();
        IntentFilter filter = new IntentFilter();
        filter.addAction(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
        filter.addAction(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED);
        
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
            mContext.registerReceiver(mReceiver, filter, Context.RECEIVER_NOT_EXPORTED);
        } else {
            mContext.registerReceiver(mReceiver, filter);
        }
    }
    
    /**
     * Headset 连接状态广播接收器
     */
    private class HeadsetReceiver extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();
            BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
            
            // Headset 设备连接状态变化
            if (BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED.equals(action)) {
                int state = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, 
                                                BluetoothProfile.STATE_DISCONNECTED);
                
                switch (state) {
                    case BluetoothProfile.STATE_CONNECTED:
                        Log.d(TAG, "Headset 设备已连接: " + device.getName());
                        // 设备已连接,可以接听电话
                        break;
                    case BluetoothProfile.STATE_DISCONNECTED:
                        Log.d(TAG, "Headset 设备已断开: " + device.getName());
                        break;
                }
            }
            
            // Headset 音频连接状态变化(通话音频通道)
            if (BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED.equals(action)) {
                int state = intent.getIntExtra(BluetoothHeadset.EXTRA_STATE, 
                                                BluetoothHeadset.STATE_AUDIO_DISCONNECTED);
                
                switch (state) {
                    case BluetoothHeadset.STATE_AUDIO_CONNECTED:
                        Log.d(TAG, "通话音频已连接: " + device.getName());
                        // 正在通过蓝牙耳机通话
                        break;
                    case BluetoothHeadset.STATE_AUDIO_DISCONNECTED:
                        Log.d(TAG, "通话音频已断开: " + device.getName());
                        // 通话结束或切换到其他音频设备
                        break;
                }
            }
        }
    }
}

10.3 BluetoothHidDevice(Human Interface Device Profile)

10.3.1 简介

HID(人机接口设备)Profile 用于连接键盘、鼠标、游戏手柄等输入设备。

10.3.2 核心特点
特性 说明
全称 Human Interface Device Profile
用途 输入设备的数据传输
传输数据 按键事件、鼠标移动、手势等
延迟要求 极低延迟(毫秒级响应)
典型设备 蓝牙键盘、蓝牙鼠标、蓝牙游戏手柄、蓝牙遥控器
10.3.3 应用场景
复制代码
┌─────────────────────────────────────────────────────────┐
│                    HID 应用场景                          │
├─────────────────────────────────────────────────────────┤
│  蓝牙键盘 ───HID───► 手机/平板(文字输入)               │
│  蓝牙鼠标 ───HID───► 手机/平板(光标控制)               │
│  游戏手柄 ───HID───► 手机/平板(游戏控制)               │
│  蓝牙遥控器 ───HID──► 电视盒子/智能电视(远程控制)      │
└─────────────────────────────────────────────────────────┘
10.3.4 代码示例
java 复制代码
/**
 * 监听 HID 设备连接状态
 * 适用设备:蓝牙键盘、蓝牙鼠标、游戏手柄、遥控器
 */
public class HidDeviceHelper {
    
    private Context mContext;
    private HidReceiver mReceiver;
    private BluetoothHidDevice mBluetoothHidDevice;
    
    /**
     * 注册 HID 服务监听
     */
    public void registerHidService() {
        BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
        if (adapter == null) {
            return;
        }
        
        // 获取 HID Profile 代理
        adapter.getProfileProxy(mContext, new BluetoothProfile.ServiceListener() {
            @Override
            public void onServiceConnected(int profile, BluetoothProfile proxy) {
                if (profile == BluetoothProfile.HID_DEVICE) {
                    mBluetoothHidDevice = (BluetoothHidDevice) proxy;
                    Log.d(TAG, "HID 服务已连接");
                    
                    // 获取已连接的 HID 设备列表
                    List<BluetoothDevice> connectedDevices = mBluetoothHidDevice.getConnectedDevices();
                    for (BluetoothDevice device : connectedDevices) {
                        Log.d(TAG, "已连接的 HID 设备: " + device.getName());
                    }
                }
            }
            
            @Override
            public void onServiceDisconnected(int profile) {
                if (profile == BluetoothProfile.HID_DEVICE) {
                    mBluetoothHidDevice = null;
                    Log.d(TAG, "HID 服务已断开");
                }
            }
        }, BluetoothProfile.HID_DEVICE);
    }
    
    /**
     * 注册 HID 连接状态广播
     */
    public void registerReceiver() {
        mReceiver = new HidReceiver();
        IntentFilter filter = new IntentFilter(BluetoothHidDevice.ACTION_CONNECTION_STATE_CHANGED);
        
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
            mContext.registerReceiver(mReceiver, filter, Context.RECEIVER_NOT_EXPORTED);
        } else {
            mContext.registerReceiver(mReceiver, filter);
        }
    }
    
    /**
     * HID 连接状态广播接收器
     */
    private class HidReceiver extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {
            BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
            int state = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, 
                                            BluetoothProfile.STATE_DISCONNECTED);
            
            switch (state) {
                case BluetoothProfile.STATE_CONNECTED:
                    Log.d(TAG, "HID 设备已连接: " + device.getName());
                    // 键盘/鼠标已连接,可以使用输入功能
                    break;
                case BluetoothProfile.STATE_DISCONNECTED:
                    Log.d(TAG, "HID 设备已断开: " + device.getName());
                    break;
            }
        }
    }
}

10.4 三种 Profile 对比总结

10.4.1 功能对比表
对比项 BluetoothA2dp BluetoothHeadset BluetoothHidDevice
Profile 名称 Advanced Audio Distribution Headset / Hands-Free Human Interface Device
主要用途 媒体音频播放 语音通话 输入设备控制
传输数据 立体声音频流 单声道语音 按键/移动数据
数据方向 单向 双向 单向或双向
典型设备 蓝牙音箱 蓝牙耳机/车载 键盘/鼠标/手柄
延迟要求 一般 极低
音频质量 中(语音级别) 不适用
10.4.2 设备连接流程对比
复制代码
A2DP 连接流程:
配对 → ACL连接 → A2DP连接 → 音频通道建立 → 可播放音乐
                              ↑
                      监听此广播最准确

Headset 连接流程:
配对 → ACL连接 → Headset连接 → (来电时)音频通道建立 → 可通话
                               ↑
                       监听此广播最准确

HID 连接流程:
配对 → ACL连接 → HID连接 → 可接收输入数据
                    ↑
            监听此广播即可
10.4.3 选择建议
你的需求 推荐使用的 Profile
监听蓝牙音箱连接状态 BluetoothA2dp
监听蓝牙耳机通话状态 BluetoothHeadset
监听车载蓝牙连接状态 BluetoothHeadset
监听蓝牙键盘/鼠标连接 BluetoothHidDevice
监听游戏手柄连接状态 BluetoothHidDevice
同时监听音乐和通话 A2dp + Headset 都需要注册

10.5 综合使用示例:监听所有类型的蓝牙设备

java 复制代码
/**
 * 综合蓝牙设备监听器
 * 同时监听 A2DP、Headset、HID 设备的连接状态
 */
public class ComprehensiveBluetoothHelper {
    
    private static final String TAG = "ComprehensiveBluetooth";
    
    private Context mContext;
    private ComprehensiveReceiver mReceiver;
    
    /**
     * 设备类型枚举
     */
    public enum DeviceType {
        A2DP,       // 音频播放设备
        HEADSET,    // 通话设备
        HID         // 输入设备
    }
    
    /**
     * 监听接口
     */
    public interface OnDeviceStateListener {
        void onDeviceConnected(BluetoothDevice device, DeviceType type);
        void onDeviceDisconnected(BluetoothDevice device, DeviceType type);
    }
    
    private OnDeviceStateListener mListener;
    
    public void setOnDeviceStateListener(OnDeviceStateListener listener) {
        this.mListener = listener;
    }
    
    /**
     * 注册所有蓝牙设备类型的广播监听
     */
    public void registerAllReceivers() {
        mReceiver = new ComprehensiveReceiver();
        
        IntentFilter filter = new IntentFilter();
        // A2DP 音频设备
        filter.addAction(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED);
        // Headset 通话设备
        filter.addAction(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
        // HID 输入设备
        filter.addAction(BluetoothHidDevice.ACTION_CONNECTION_STATE_CHANGED);
        
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
            mContext.registerReceiver(mReceiver, filter, Context.RECEIVER_NOT_EXPORTED);
        } else {
            mContext.registerReceiver(mReceiver, filter);
        }
    }
    
    /**
     * 注销广播监听
     */
    public void unregisterAllReceivers() {
        if (mReceiver != null) {
            mContext.unregisterReceiver(mReceiver);
            mReceiver = null;
        }
    }
    
    /**
     * 综合广播接收器
     */
    private class ComprehensiveReceiver extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {
            if (intent == null || intent.getAction() == null) {
                return;
            }
            
            String action = intent.getAction();
            BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
            int state = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, 
                                            BluetoothProfile.STATE_DISCONNECTED);
            
            if (device == null) return;
            
            DeviceType type = null;
            
            // 判断是哪种类型的设备
            if (BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED.equals(action)) {
                type = DeviceType.A2DP;
            } else if (BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED.equals(action)) {
                type = DeviceType.HEADSET;
            } else if (BluetoothHidDevice.ACTION_CONNECTION_STATE_CHANGED.equals(action)) {
                type = DeviceType.HID;
            }
            
            if (type == null) return;
            
            // 回调状态变化
            if (mListener != null) {
                if (state == BluetoothProfile.STATE_CONNECTED) {
                    mListener.onDeviceConnected(device, type);
                } else if (state == BluetoothProfile.STATE_DISCONNECTED) {
                    mListener.onDeviceDisconnected(device, type);
                }
            }
        }
    }
}

十一、总结

本文详细介绍了 Android 蓝牙设备连接广播的相关知识,包括:

  1. 蓝牙权限的正确配置方式

  2. 蓝牙连接状态相关的广播 Action 及其区别

  3. 关键要点

    • ACTION_ACL_CONNECTED 在配对后触发,不适合用于判断音频连接状态
    • BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED 不支持多设备场景
    • BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED 推荐使用,支持多设备且状态准确
  4. 广播接收器的静态和动态注册方法

    其实很多蓝牙的广播是不能静态注册的,上面有些内容是AI生成的。

    可以具体看:

    Android13 不能静态注册的几个广播:

    https://blog.csdn.net/wenzhi20102321/article/details/13495609

    Android13 研究可以静态注册的广播:

    https://blog.csdn.net/wenzhi20102321/article/details/149268983

  5. 如何从广播中获取蓝牙设备信息

  6. 经典蓝牙与 BLE 的区别

  7. 三种蓝牙 Profile 详解

    • A2DP:用于蓝牙音箱、耳机听歌,高质量立体声音频
    • Headset:用于蓝牙耳机通话、车载蓝牙,双向语音通信
    • HID:用于蓝牙键盘、鼠标、游戏手柄,低延迟输入设备
  8. Android 版本适配注意事项

  9. 完整的工具类封装及使用示例

掌握这些知识,可以帮助开发者正确处理蓝牙设备的连接状态变化,根据不同设备类型选择合适的监听方式,避免常见的问题和陷阱。


参考资料

内网有时候打不开,或者打开很慢。

相关推荐
楼田莉子1 小时前
Docker学习:Docker介绍及其架构介绍
运维·后端·学习·docker·容器·架构
郝学胜-神的一滴1 小时前
干货版《算法导论》07:递归视角下的选择排序与归并排序
java·数据结构·c++·python·程序人生·算法·排序算法
shehuiyuelaiyuehao2 小时前
多线程入门
java·python·算法
星夜夏空992 小时前
FreeRTOS学习(7)——任务列表
java·前端·学习
不羁的木木2 小时前
Form Kit(卡片开发服务)学习笔记01-核心概念与架构设计
笔记·学习·harmonyos
Mikowoo0072 小时前
神经网络 替代 线性模型_进行模型学习
人工智能·神经网络·学习
不羁的木木2 小时前
ArkWeb实战学习笔记01-核心概念与架构设计
笔记·学习·harmonyos
Oo9202 小时前
Prompt工程核心与Python 字典
python·ai编程
feeday2 小时前
gpt4o 图像反推提示词
开发语言·人工智能·python