Android 12蓝牙权限大改,你的App还好吗?手把手教你适配BLUETOOTH_SCAN/CONNECT
Android 12蓝牙权限适配实战:从崩溃到兼容的全方位指南
最近不少开发者反馈,原本运行良好的蓝牙应用在用户升级到Android 12或HarmonyOS 3.0后突然无法正常工作。这背后是Android 12对蓝牙权限体系的一次重大重构。本文将带你深入理解这次变更的技术细节,并提供一套完整的适配方案。
1. 理解Android 12蓝牙权限变更
Android 12将原先简单的BLUETOOTH和BLUETOOTH_ADMIN权限拆分为三个更细粒度的运行时权限:
- BLUETOOTH_SCAN:用于发现附近蓝牙设备
- BLUETOOTH_CONNECT:用于连接已配对设备
- BLUETOOTH_ADVERTISE:允许本设备被其他设备发现
这种变化反映了Android权限系统向更精细控制的发展趋势。与位置权限类似,新的蓝牙权限也需要在运行时动态申请,而不仅仅是在AndroidManifest.xml中声明。
关键变化对比表:
| 功能 | Android 11及以下 | Android 12+ |
|---|---|---|
| 扫描设备 | BLUETOOTH_ADMIN | BLUETOOTH_SCAN |
| 连接设备 | BLUETOOTH | BLUETOOTH_CONNECT |
| 广播设备 | BLUETOOTH_ADMIN | BLUETOOTH_ADVERTISE |
| 权限类型 | 普通权限 | 运行时权限 |
2. 兼容性适配方案
2.1 AndroidManifest配置
首先需要在AndroidManifest.xml中正确声明权限,确保兼容新旧版本:
<!-- 旧版本权限,仅适用于API 30及以下 --> <uses-permission android:name="android.permission.BLUETOOTH" android:maxSdkVersion="30" /> <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" android:maxSdkVersion="30"/> <!-- 新版本权限 --> <uses-permission android:name="android.permission.BLUETOOTH_SCAN" /> <uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" /> <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" /> <!-- 如果涉及设备发现,仍需位置权限 --> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" android:maxSdkVersion="30"/>注意:即使你的应用targetSdkVersion低于31,当运行在Android 12+设备上时,新权限规则仍然适用。
2.2 运行时权限申请
动态权限申请需要处理Android 12+的特殊情况:
fun checkBluetoothPermissions(activity: Activity) { val permissionsToRequest = mutableListOf<String>() if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { // Android 12+需要新权限 if (ContextCompat.checkSelfPermission( activity, Manifest.permission.BLUETOOTH_SCAN) != PackageManager.PERMISSION_GRANTED) { permissionsToRequest.add(Manifest.permission.BLUETOOTH_SCAN) } // 根据需要添加CONNECT和ADVERTISE } else { // 旧版本处理 if (ContextCompat.checkSelfPermission( activity, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) { permissionsToRequest.add(Manifest.permission.ACCESS_FINE_LOCATION) } } if (permissionsToRequest.isNotEmpty()) { ActivityCompat.requestPermissions( activity, permissionsToRequest.toTypedArray(), BLUETOOTH_PERMISSION_REQUEST_CODE ) } }2.3 权限组特性利用
Android 12的三个新蓝牙权限属于同一个权限组,这意味着:
- 用户只需同意其中一个权限,系统会自动授予同组的其他权限
- 但最佳实践是明确申请你实际需要的所有权限
- 权限对话框会显示你请求的所有权限,增加用户信任度
3. 实际开发中的常见问题
3.1 后台扫描限制
Android 12对后台蓝牙扫描增加了新的限制:
- 前台服务扫描需要声明
android:usesPermissionFlags="neverForLocation" - 后台扫描需要额外声明
ACCESS_BACKGROUND_LOCATION - 必须提供合理的用途说明
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" android:usesPermissionFlags="neverForLocation" />3.2 旧代码迁移策略
对于已有蓝牙功能的迁移,建议采用以下步骤:
- 功能分析:明确你的应用需要哪些蓝牙功能
- 权限映射:将旧权限对应到新权限
- 版本检测:添加适当的版本分支逻辑
- 用户引导:准备清晰的权限申请说明
3.3 测试策略
全面的测试方案应该包括:
- 不同Android版本设备测试
- 权限拒绝场景处理
- 后台行为验证
- 权限变化后的恢复逻辑
4. 高级技巧与最佳实践
4.1 最小化权限请求
只请求应用真正需要的权限:
- 如果只连接已配对设备,可能不需要SCAN权限
- 如果只是外围设备,可能只需要ADVERTISE
- 考虑使用
neverForLocation标志减少用户疑虑
4.2 优雅的降级处理
当用户拒绝权限时,提供有意义的反馈:
override fun onRequestPermissionsResult( requestCode: Int, permissions: Array<out String>, grantResults: IntArray ) { when (requestCode) { BLUETOOTH_PERMISSION_REQUEST_CODE -> { if (grantResults.all { it == PackageManager.PERMISSION_GRANTED }) { // 权限已授予,继续蓝牙操作 } else { // 解释为什么需要这些权限 showRationaleDialog() } } } }4.3 跨版本兼容库
考虑创建一个蓝牙兼容层,封装版本差异:
object BluetoothCompat { fun checkPermissions(context: Context): Boolean { return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { ContextCompat.checkSelfPermission( context, Manifest.permission.BLUETOOTH_SCAN) == PackageManager.PERMISSION_GRANTED } else { ContextCompat.checkSelfPermission( context, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED } } // 其他兼容方法... }在实际项目中,我发现很多开发者容易忽略对新权限的持续状态监控。建议在应用的基类Activity中重写onResume(),检查权限状态变化,确保UI与当前权限状态同步。
