第2章:AndroidManifest权限声明适配
2.1 权限声明演进背景
从 Android 12(API 31)开始,Android 逐步对蓝牙相关的运行时权限进行拆分,并在 Android 13(API 33)中完成了最终的权限模型重构。旧版的 BLUETOOTH 与 BLUETOOTH_ADMIN 权限被细化为三个独立的新权限:
- BLUETOOTH_SCAN:扫描蓝牙设备(无需位置权限)
- BLUETOOTH_CONNECT:连接已配对的蓝牙设备、收发数据
- BLUETOOTH_ADVERTISE:广播蓝牙 LE 广告
同时,新增 android:permissionFlags 属性用于控制权限的语义行为(如“不用于位置推断”标记)。适配时必须满足 compileSdk 33+ 且 targetSdk 33+ 才能完整使用新权限模型。
2.2 新旧权限声明方式对比
| 权限类型 | 旧声明(API 30 及以下) | 新声明(API 33+) |
|---|---|---|
| 扫描 | BLUETOOTH + ACCESS_FINE_LOCATION | BLUETOOTH_SCAN(可选不关联位置) |
| 连接 | BLUETOOTH | BLUETOOTH_CONNECT |
| 广告 | 无专用权限,依赖 BLUETOOTH_ADMIN | BLUETOOTH_ADVERTISE |
| 传统管理 | BLUETOOTH_ADMIN | 不再推荐,但保留向后兼容 |
2.3 新增权限声明方式
在 AndroidManifest.xml 中,新权限必须通过 <uses-permission> 标签声明,并配合 android:maxSdkVersion 控制向下兼容。示例:
<!-- 新权限声明(API 33+) -->
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />
<!-- 向下兼容旧版本时,保留旧权限并限制 maxSdkVersion -->
<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.ACCESS_FINE_LOCATION"
android:maxSdkVersion="32" />
注意:android:maxSdkVersion 仅对 manifest 级别的声明 生效,运行时权限请求依然受 targetSdk 影响。
2.4 android:permissionFlags 标记
Android 13 引入了 permissionFlags 属性,允许在声明权限时附加行为标记。最常用的标记是 neverForLocation,用于告诉系统此 BLUETOOTH_SCAN 权限绝不会被用于获取用户位置。添加该标记后,系统将不会对扫描请求强制要求位置权限,从而减少用户授权负担。
声明示例:
<uses-permission android:name="android.permission.BLUETOOTH_SCAN"
android:usesPermissionFlags="neverForLocation" />
其他标记(如 installer、signature 等)在蓝牙场景中较少使用,但需注意 neverForLocation 必须与 BLUETOOTH_SCAN 配合,且不得同时声明 ACCESS_FINE_LOCATION 或 ACCESS_COARSE_LOCATION 权限(否则标记失效)。
2.5 compileSdk 33+ 要求
若要使用新权限类型和 permissionFlags,项目的 compileSdk 必须设置为 33 或更高。在 build.gradle 中配置:
android {
compileSdk 34 // 或 33+
defaultConfig {
...
targetSdk 33 // 或更高
}
}
若 compileSdk 低于 33,则无法识别 BLUETOOTH_SCAN 等权限名称,编译时会报 “Permission is only defined in SDK 33+” 错误。
2.6 targetSdk 33+ 适配
当 targetSdk < 33 时,系统会使用旧的权限模型(将所有蓝牙权限映射回 BLUETOOTH 和 BLUETOOTH_ADMIN),新权限声明会被忽略。建议将 targetSdk 升级到 33+ 以启用新的运行时权限拆分,否则即使声明了新权限,运行时仍按旧权限逻辑处理(可能多出不必要的定位授权要求)。
适配策略:
- 若应用仍需支持 Android 12 及以下设备,保留旧权限声明并设置
maxSdkVersion。 - 在运行时根据 API 版本动态请求对应权限(见第5章运行时适配)。
2.7 调试与验证
使用 adb shell dumpsys package 查看应用的已授予权限列表:
adb shell dumpsys package com.example.yourapp | grep -E "BLUETOOTH|permission"
检查输出中是否包含 BLUETOOTH_SCAN 等新权限名称。若 targetSdk < 33,这些新权限将不会出现在运行时授权记录中。
2.8 常见错误
| 错误类型 | 现象 | 解决方案 |
|---|---|---|
| 编译错误:权限未定义 | “BLUETOOTH_SCAN is not a valid permission” | 将 compileSdk 提升到 33+ |
| 运行时权限无提示 | 请求 BLUETOOTH_SCAN 但系统无弹窗 | 检查 targetSdk ≥ 33,且未添加位置权限声明 |
| neverForLocation 失效 | 仍被要求授予位置权限 | 移除所有 ACCESS_FINE_LOCATION 声明,且确保应用未请求位置权限 |
| 旧权限冲突 | 同时声明 BLUETOOTH 和 BLUETOOTH_SCAN | 使用 maxSdkVersion 限制旧权限只在 API 30-32 生效 |
2.9 性能与稳定性优化
- 最小化权限声明:只声明应用实际使用的蓝牙权限,避免冗余请求增加用户授权拒绝概率。
- 动态切换:对于只在特定功能中使用的扫描权限,可以考虑在运行时按需添加
neverForLocation标记(若通过<uses-permission>静态声明则无法动态移除标记,故静态声明时应谨慎)。 - 向后兼容:使用
maxSdkVersion确保低版本设备仍能正常工作,避免因权限缺失导致崩溃。