别再只申请位置权限了!Android蓝牙开发完整权限申请指南(附兼容代码)
Android蓝牙开发权限全解析:从兼容性陷阱到最佳实践
每次在Android项目中集成蓝牙功能时,开发者总会遇到那个经典问题:"为什么我的蓝牙扫描在Android 10设备上突然失效了?"这通常不是代码逻辑的问题,而是权限体系的暗礁。让我们从实际案例出发,看看如何避开这些陷阱。
1. 蓝牙权限的版本演进与核心概念
Android的蓝牙权限体系经历了三次重大变革。在Android 5.0及之前版本中,只需要在AndroidManifest.xml中声明两个普通权限:
<uses-permission android:name="android.permission.BLUETOOTH" /> <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />但从Android 6.0开始,系统引入了一个令人困惑的关联规则:使用蓝牙扫描功能时,必须同时获取位置权限。这是因为蓝牙扫描可能被用于位置追踪(通过蓝牙信标),所以系统将其归类为敏感操作。
到Android 12时,Google彻底重构了蓝牙权限模型,将其细分为三个新的运行时权限:
| 权限名称 | 用途 | 最低API等级 |
|---|---|---|
| BLUETOOTH_SCAN | 扫描附近设备 | 31 |
| BLUETOOTH_CONNECT | 连接已配对设备 | 31 |
| BLUETOOTH_ADVERTISE | 作为外围设备广播 | 31 |
关键区别:
- 旧权限是安装时授予的
- 新权限需要运行时动态申请
- 位置权限要求仍然存在(针对扫描场景)
2. 全版本兼容的权限声明方案
要实现真正的版本兼容,需要在AndroidManifest.xml中做如下声明:
<!-- 基础蓝牙权限(Android 5.0及以下) --> <uses-permission android:name="android.permission.BLUETOOTH" android:maxSdkVersion="30" /> <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" android:maxSdkVersion="30" /> <!-- 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 6.0+ 蓝牙扫描需要) --> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" android:maxSdkVersion="30" />注意:从Android 12开始,如果声明了
neverForLocation用途,可以免除位置权限要求
3. 动态权限申请的最佳实践
动态权限申请需要处理多种情况,下面是一个完整的实现示例:
private fun checkBluetoothPermissions() { val permissionsToRequest = mutableListOf<String>() if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { // Android 12+ 需要新权限 if (!hasPermission(Manifest.permission.BLUETOOTH_SCAN)) { permissionsToRequest.add(Manifest.permission.BLUETOOTH_SCAN) } if (!hasPermission(Manifest.permission.BLUETOOTH_CONNECT)) { permissionsToRequest.add(Manifest.permission.BLUETOOTH_CONNECT) } } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { // Android 6.0-11 需要位置权限 if (!hasPermission(Manifest.permission.ACCESS_FINE_LOCATION)) { permissionsToRequest.add(Manifest.permission.ACCESS_FINE_LOCATION) } } if (permissionsToRequest.isNotEmpty()) { requestPermissions(permissionsToRequest.toTypedArray(), BLUETOOTH_PERMISSION_CODE) } else { startBluetoothOperation() } } private fun hasPermission(permission: String): Boolean { return ContextCompat.checkSelfPermission(this, permission) == PackageManager.PERMISSION_GRANTED }常见问题处理:
- 用户拒绝权限后的引导
- 永久拒绝时的处理策略
- 权限组自动授予的特殊情况
4. 实际场景中的权限策略
不同蓝牙操作需要的权限组合:
| 操作类型 | Android 5.0-5.1 | Android 6.0-11 | Android 12+ |
|---|---|---|---|
| 扫描设备 | BLUETOOTH_ADMIN | BLUETOOTH_ADMIN + ACCESS_FINE_LOCATION | BLUETOOTH_SCAN (可选位置) |
| 连接设备 | BLUETOOTH | BLUETOOTH | BLUETOOTH_CONNECT |
| 作为外设广播 | BLUETOOTH_ADMIN | BLUETOOTH_ADMIN | BLUETOOTH_ADVERTISE |
性能优化建议:
- 延迟权限请求直到真正需要时
- 对BLUETOOTH_SCAN使用
neverForLocation标记 - 合理处理权限拒绝后的降级流程
5. 测试与调试技巧
确保覆盖以下测试场景:
- 不同API级别的设备
- 权限被拒绝的情况
- 从旧版本升级到新版本
- 后台扫描的限制
调试时可以使用adb命令快速重置权限状态:
adb shell pm reset-permissions <package_name>在实现蓝牙功能时,我曾经遇到过这样一个案例:应用在Android 9设备上运行良好,但在Android 10设备上突然无法发现任何设备。经过排查,发现是忘记处理位置权限被拒绝的情况。这个教训让我意识到,完善的权限处理不仅是功能需求,更是用户体验的关键部分。
