第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_LOCATIONBLUETOOTH_SCAN(可选不关联位置)
连接BLUETOOTHBLUETOOTH_CONNECT
广告无专用权限,依赖 BLUETOOTH_ADMINBLUETOOTH_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 确保低版本设备仍能正常工作,避免因权限缺失导致崩溃。

2.10 权限声明适配流程(SVG 流程图)

AndroidManifest 蓝牙权限声明适配流程 AndroidManifest 蓝牙权限声明适配流程 确定项目 compileSdk compileSdk ≥ 33? 是 声明新权限 添加 permissionFlags targetSdk ≥ 33? 是 新权限生效 否 旧权限模型 否 编译错误 注意事项 ① compileSdk < 33 时无法识别新权限,必须升级;② targetSdk < 33 时新权限声明不生效,系统使用旧权限映射;③ neverForLocation 标记仅适用于 BLUETOOTH_SCAN,且需避免同时声明位置权限。

2.11 小结