
1. Android 12蓝牙权限变更背景最近不少开发者反馈原本运行良好的蓝牙功能在Android 12及以上系统突然失效了。这个问题不仅出现在原生Android系统HarmonyOS 3.0.0也同样存在。经过排查发现根本原因是Android 12对蓝牙权限模型进行了重大调整。在Android 11及之前版本我们只需要在AndroidManifest.xml中声明两个蓝牙权限android.permission.BLUETOOTHandroid.permission.BLUETOOTH_ADMIN但从Android 12开始Google将这两个权限拆分为三个新的运行时权限BLUETOOTH_SCAN用于扫描附近的蓝牙设备BLUETOOTH_ADVERTISE允许本设备被其他蓝牙设备发现BLUETOOTH_CONNECT用于连接已配对的蓝牙设备这个变更带来的最大挑战是新权限全部变成了运行时权限dangerous permission这意味着仅靠静态声明已经不够必须在代码中动态申请用户授权。2. 兼容性适配方案2.1 权限声明配置为了确保应用在Android 12及以下版本都能正常工作我们需要在AndroidManifest.xml中做如下配置!-- 兼容Android 11及以下版本 -- uses-permission android:nameandroid.permission.BLUETOOTH android:maxSdkVersion30 / uses-permission android:nameandroid.permission.BLUETOOTH_ADMIN android:maxSdkVersion30/ !-- Android 12新增权限 -- uses-permission android:nameandroid.permission.BLUETOOTH_SCAN / uses-permission android:nameandroid.permission.BLUETOOTH_ADVERTISE / uses-permission android:nameandroid.permission.BLUETOOTH_CONNECT /这里的关键点是使用maxSdkVersion属性确保旧权限只在需要的版本生效。我在实际项目中遇到过因为没有设置maxSdkVersion导致权限冲突的情况这点需要特别注意。2.2 运行时权限申请新增的三个蓝牙权限都属于同一个权限组这意味着只要用户授予了其中一个权限系统会自动授予同组的其他权限。下面是动态申请的标准做法private void requestBluetoothPermissions() { if (Build.VERSION.SDK_INT Build.VERSION_CODES.S) { ListString permissionsToRequest new ArrayList(); if (ContextCompat.checkSelfPermission(this, Manifest.permission.BLUETOOTH_SCAN) ! PackageManager.PERMISSION_GRANTED) { permissionsToRequest.add(Manifest.permission.BLUETOOTH_SCAN); } if (ContextCompat.checkSelfPermission(this, Manifest.permission.BLUETOOTH_CONNECT) ! PackageManager.PERMISSION_GRANTED) { permissionsToRequest.add(Manifest.permission.BLUETOOTH_CONNECT); } if (!permissionsToRequest.isEmpty()) { ActivityCompat.requestPermissions(this, permissionsToRequest.toArray(new String[0]), BLUETOOTH_PERMISSION_REQUEST_CODE); } } }在实际测试中发现有些厂商的ROM对权限处理有特殊逻辑建议在onRequestPermissionsResult中检查每个权限的授权状态而不是依赖权限组的自动授权特性。3. 地理位置权限的特殊处理3.1 历史遗留问题在Android 12之前使用蓝牙扫描功能时还需要一个看似不相关的权限ACCESS_FINE_LOCATION。这是因为蓝牙扫描可以用于获取设备位置信息比如通过Beacon设备。这个要求让很多开发者困惑我在早期开发蓝牙应用时也踩过这个坑。明明只是想做设备间通信却不得不申请位置权限导致用户经常质疑应用为何需要定位功能。3.2 Android 12的改进Android 12引入了一个重要特性当应用只需要进行蓝牙设备扫描而不需要获取位置信息时可以在AndroidManifest.xml中添加usesPermissionFlags属性uses-permission android:nameandroid.permission.BLUETOOTH_SCAN android:usesPermissionFlagsneverForLocation /这样配置后系统会知道你的应用仅将蓝牙扫描用于设备发现而非定位就不再强制要求位置权限了。不过要注意如果确实需要获取设备位置信息还是需要同时申请位置权限。4. 实战中的常见问题与解决方案4.1 权限申请被拒绝后的处理用户可能会拒绝授予蓝牙权限这时候我们需要优雅地处理这种情况。建议的做法是在首次拒绝时展示解释对话框说明权限的必要性如果用户再次拒绝考虑降级功能或提供替代方案使用shouldShowRequestPermissionRationale判断是否需要解释Override public void onRequestPermissionsResult(int requestCode, NonNull String[] permissions, NonNull int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); if (requestCode BLUETOOTH_PERMISSION_REQUEST_CODE) { for (int i 0; i permissions.length; i) { if (grantResults[i] ! PackageManager.PERMISSION_GRANTED) { if (shouldShowRequestPermissionRationale(permissions[i])) { // 展示解释对话框 showPermissionRationaleDialog(); } else { // 用户勾选了不再询问 handlePermissionPermanentlyDenied(); } } } } }4.2 后台使用限制Android 12对后台使用蓝牙功能增加了限制。如果应用需要在后台执行蓝牙操作除了上述权限外还需要声明uses-permission android:nameandroid.permission.BLUETOOTH_SCAN android:usesPermissionFlagsneverForLocation|allowInBackground /但要注意后台蓝牙功能的使用会受到系统更严格的管控可能会影响电池续航。在实际项目中建议尽可能减少后台蓝牙操作或者使用WorkManager等机制来优化执行时机。4.3 多设备兼容性测试由于各厂商对Android系统的定制程度不同蓝牙权限的实际表现可能存在差异。我建议在以下场景进行充分测试不同Android版本特别是11到12的过渡不同厂商ROM小米、华为、三星等不同蓝牙设备类型耳机、手环、Beacon等应用前后台状态切换时的权限保持测试时可以使用adb命令快速重置权限状态adb shell pm reset-permissions adb shell pm clear package-name5. 最佳实践建议经过多个项目的实践验证我总结出以下蓝牙权限处理的最佳实践分层请求策略不要一次性请求所有权限而是根据功能需要逐步请求。比如先请求CONNECT权限建立连接等用户需要扫描设备时再请求SCAN权限。权限状态缓存将权限状态缓存在SharedPreferences中避免频繁检查。但要注意在onResume等关键生命周期更新缓存。降级体验设计当缺少某些权限时应用仍应提供基本功能。比如没有SCAN权限时可以显示已配对设备列表而不是空白页面。厂商特殊处理针对华为等厂商设备可能需要额外检查HMS Core的权限状态。持续监控使用Android Vitals监控权限拒绝率对高拒绝率的权限优化请求时机和说明文案。在最近的一个智能家居项目中我们通过优化权限请求流程将蓝牙权限的接受率从60%提升到了92%。关键是在用户真正需要使用蓝牙功能时才弹出请求并提供了清晰的解释说明。