第3章 蓝牙经典权限:BLUETOOTH与BLUETOOTH_ADMIN

蓝牙经典权限(BLUETOOTH 与 BLUETOOTH_ADMIN)是 Android 平台早在 API 级别 5(Android 2.0)引入的基础蓝牙操作权限。虽然 Android 12(API 31)之后新增了扫描/连接/广播细粒度权限,但旧版经典权限在 targetSdkVersion < 31 的应用中仍然广泛使用,且是许多遗留系统和车载应用的核心适配对象。

3.1 权限声明方式

在 AndroidManifest.xml 中使用 <uses-permission> 标签声明,两个权限通常成对出现。

<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    <!-- 允许应用连接到已配对的蓝牙设备 -->
    <uses-permission android:name="android.permission.BLUETOOTH" />

    <!-- 允许应用发现本地蓝牙设备或修改蓝牙设置 -->
    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
</manifest>
权限名称 API 常量 保护级别 引入 API
BLUETOOTH android.permission.BLUETOOTH Dangerous(旧)/ Normal(实际运行时无需弹窗) 5
BLUETOOTH_ADMIN android.permission.BLUETOOTH_ADMIN Dangerous(旧)/ Normal 5
⚠ 注意:在 Android 6.0+ 且 targetSdkVersion ≥ 23 时,BLUETOOTH 与 BLUETOOTH_ADMIN 仍然是 Normal 级别权限,系统会在安装时自动授予,不会触发运行时权限弹窗。但从 Android 12 开始,必须同时声明 BLUETOOTH_SCAN、BLUETOOTH_CONNECT 和 BLUETOOTH_ADVERTISE 才能进行蓝牙操作,旧权限已不足以覆盖扫描和连接行为。

3.2 作用域

两个权限各自控制蓝牙操作的不同环节,下表总结了每个权限覆盖的 API 范围:

权限 允许的操作 典型 API 示例
BLUETOOTH
  • 请求连接、接受连接
  • 传输数据(RFCOMM)
  • 获取已配对设备列表
  • 查询本地蓝牙适配器状态
BluetoothAdapter.getDefaultAdapter()
BluetoothDevice.createRfcommSocketToServiceRecord()
BluetoothAdapter.getBondedDevices()
BLUETOOTH_ADMIN
  • 开启/关闭蓝牙
  • 发起设备发现(扫描)
  • 修改蓝牙设置(如可检测性)
  • 创建配对/取消配对
BluetoothAdapter.enable()
BluetoothAdapter.startDiscovery()
BluetoothAdapter.setScanMode()
BluetoothDevice.createBond()
💡 最佳实践:即使 targetSdkVersion < 31,也建议在清单中同时声明这两个权限。缺少 BLUETOOTH 会导致连接失败,缺少 BLUETOOTH_ADMIN 会导致 startDiscovery() 和 enable() 抛出 SecurityException。

3.3 特殊说明与限制

3.3.1 运行时权限的特殊表现

尽管权限保护级别标记为 Dangerous,但 Android 系统对这两个权限采用了特殊处理:

  • 不弹运行时授权对话框:安装时自动授予,用户无法通过系统设置单独撤销。
  • 不触发运行时请求流程:调用 ActivityCompat.requestPermissions() 不会弹出 UI。
  • Android 12+ 行为变更:如果 targetSdkVersion ≥ 31,系统会忽略 BLUETOOTH/BLUETOOTH_ADMIN 的声明,必须改用新的 BLUETOOTH_SCAN/BLUETOOTH_CONNECT,否则操作直接返回失败。
注意点:对于仍适配 Android 11 及以下版本的旧应用,BLUETOOTH 和 BLUETOOTH_ADMIN 仍然是安全且有效的声明方式。但若需支持 Android 12+ 设备,则必须主动迁移至新的蓝牙权限体系。

3.3.2 权限与设备发现(Discovery)的关系

BLUETOOTH_ADMIN 权限仅允许发起 发现过程,但发现过程中的 广播接收(ACTION_FOUND)还需要 BLUETOOTH 权限才能获取设备信息。

// 错误示例:仅有 BLUETOOTH_ADMIN 时无法获取扫描结果
IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
registerReceiver(receiver, filter);
// 如果没有 BLUETOOTH 权限,receiver 不会接收任何广播

3.3.3 配对(Bonding)流程中的权限要求

配对过程中系统自动处理大部分 UI,但应用调用 createBond() 需要 BLUETOOTH_ADMIN 权限。配对完成后,getBondedDevices() 需要 BLUETOOTH 权限。

3.4 蓝牙经典权限声明与使用流程

下图展示了经典权限在应用启动、蓝牙操作和异常处理中的完整生命周期:

蓝牙经典权限声明与使用流程图 AndroidManifest.xml targetSdkVersion ≥ 31(废弃旧权限) < 31(使用旧权限) 声明 BLUETOOTH_SCAN/BLUETOOTH_CONNECT 声明 BLUETOOTH + BLUETOOTH_ADMIN 安装时自动授权(Normal) 调用蓝牙 API SecurityException 兜底 说明 左侧分支表示 Android 12+ 的新权限模型, 右侧分支表示旧版经典权限模型。

3.5 异常处理与调试

缺少 BLUETOOTH 或 BLUETOOTH_ADMIN 权限时,系统会抛出 SecurityException,典型日志如下:

java.lang.SecurityException: Need BLUETOOTH permission: Neither user 10123
nor current process has android.permission.BLUETOOTH.
❌ 常见错误:在 Android 12 设备上保留旧权限声明但未添加新权限,运行时直接返回 BLUETOOTH_ADAPTER.STATE_OFF 或扫描结果为空,不抛异常。这种情况非常隐蔽,排查时优先检查 targetSdkVersion 和权限声明组合。

调试步骤:

  1. 通过 adb shell dumpsys package PACKAGE_NAME | grep permission 查看已授予权限列表。
  2. 确认 BLUETOOTH 和 BLUETOOTH_ADMIN 是否已出现在 granted 列表中。
  3. 若 targetSdkVersion ≥ 31,检查是否额外声明了 BLUETOOTH_SCAN 和 BLUETOOTH_CONNECT。
  4. 调用 checkSelfPermission() 在运行时验证权限状态,即使不弹窗也可用于排查。
// 运行时检查经典权限(即使不弹窗)
int result = ContextCompat.checkSelfPermission(
    context,
    Manifest.permission.BLUETOOTH
);
if (result != PackageManager.PERMISSION_GRANTED) {
    // 正常不会进入此分支,但 Android 12+ 迁移时可能发生
    Log.w("BT_Permission", "BLUETOOTH 未授予,需检查 targetSdkVersion");
}

3.6 性能与稳定性优化

场景 优化建议
频繁调用 enable()/disable() 每次切换会触发系统蓝牙服务重启,建议用 getState() 先判断当前状态,避免重复开关。
长时间扫描未停止 即使使用旧权限,扫描也是高功耗操作;建议设置超时(如 12 秒),或监听 ACTION_DISCOVERY_FINISHED 自动取消。
多线程并发连接 BLUETOOTH 权限不限制连接数,但 RFCOMM 连接是串行通道。建议使用连接池或队列管理,防止资源竞争。
权限声明冗余 若不需修改蓝牙设置(如仅读取已配对设备),可只声明 BLUETOOTH,减少权限审核风险。