第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 |
|
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 蓝牙经典权限声明与使用流程
下图展示了经典权限在应用启动、蓝牙操作和异常处理中的完整生命周期:
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 和权限声明组合。
调试步骤:
- 通过
adb shell dumpsys package PACKAGE_NAME | grep permission查看已授予权限列表。 - 确认
BLUETOOTH和BLUETOOTH_ADMIN是否已出现在 granted 列表中。 - 若 targetSdkVersion ≥ 31,检查是否额外声明了
BLUETOOTH_SCAN和BLUETOOTH_CONNECT。 - 调用
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,减少权限审核风险。 |