蓝牙权限配置
本页介绍 Jieli-Tech/android-bt-demo 仓库中 ATTConnect 示例工程所需的全部蓝牙相关权限声明(Manifest 声明)与权限模型,涵盖 Android 12+ 新增的运行时蓝牙权限、经典蓝牙权限、BLE 扫描所需的定位权限以及存储权限。
Purpose and Scope
本页聚焦于"蓝牙权限配置"这一入门主题,具体说明:
ATTConnect/app/src/main/AndroidManifest.xml中声明的所有权限及其用途- 不同 Android 版本(≤11 与 12+)下蓝牙权限模型的差异
- 经典蓝牙(BR/EDR)与低功耗蓝牙(BLE)扫描、连接所需权限的区别
- 定位权限与存储权限为何与蓝牙功能强相关
与权限配置相关的实际使用(扫描、连接、数据收发)属于本入门系列的其他页面,例如 BLE 扫描与连接流程请参见对应的 ATT 连接示例页面;本页只负责"权限如何配置"这一主题。若源码中运行时权限申请逻辑在后续版本补充,本页将同步更新。
Overview
Android 系统对蓝牙功能的权限管控经历了多次演进,开发者需要同时考虑 Manifest 静态声明 与 运行时动态申请 两层要求:
- Android 11(API 30)及以下:蓝牙使用
BLUETOOTH与BLUETOOTH_ADMIN两个普通权限,只需在 Manifest 中声明即可,无需运行时申请;但 BLE 扫描会返回空结果(需要定位权限才能发现设备),因此还需要ACCESS_FINE_LOCATION(或ACCESS_COARSE_LOCATION)并在运行时动态申请。 - Android 12(API 31)及以上:Google 将蓝牙权限细化为三个运行时权限——
BLUETOOTH_SCAN(扫描)、BLUETOOTH_CONNECT(连接)、BLUETOOTH_ADVERTISE(广播),均属于危险权限,必须在运行时向用户动态申请;同时定位权限依然影响 BLE 扫描结果。
本仓库的示例工程在 Manifest 中同时声明了新旧两套蓝牙权限,这样同一个 APK 可以兼容 Android 11 及以下(忽略未知权限声明)与 Android 12+(使用新的运行时权限)两类设备。tools:targetApi="31" 的声明也表明工程已按 Android 12 的权限模型进行适配。
Architecture
下图展示了本示例工程的权限声明结构与权限使用方的关系:
flowchart TD
subgraph sg_Manifest["AndroidManifest.xml"]
subgraph sg_Classic["经典蓝牙权限 (Android ≤ 11)"]
P1["BLUETOOTH"]
P2["BLUETOOTH_ADMIN"]
end
subgraph sg_Runtime["运行时蓝牙权限 (Android 12+)"]
P3["BLUETOOTH_SCAN"]
P4["BLUETOOTH_CONNECT"]
end
subgraph sg_Location["定位权限 (BLE 扫描依赖)"]
P5["ACCESS_COARSE_LOCATION"]
P6["ACCESS_FINE_LOCATION"]
end
subgraph sg_Storage["存储权限 (文件传输)"]
P7["READ_EXTERNAL_STORAGE"]
P8["WRITE_EXTERNAL_STORAGE"]
end
P9["INTERNET"]
end
subgraph sg_App["ATTConnect 应用"]
A1["HomeActivity (启动入口)"]
A2["CommonActivity"]
A3["BleManager (BLE 扫描/连接)"]
A4["BluetoothUtil (工具类)"]
A5["SendBleDataThread (数据发送)"]
end
P1 --> A3
P2 --> A3
P3 --> A3
P4 --> A3
P5 --> A3
P6 --> A3
P7 --> A5
P8 --> A5
P9 --> A5
A1 --> A2
A2 --> A3
架构说明:
- Manifest 层按权限类别分组:经典蓝牙权限、Android 12+ 运行时权限、定位权限、存储权限、网络权限。新旧权限同时声明,保证 APK 在不同 Android 版本上都能满足系统校验。
- 应用层中,
BleManager(BleManager.java)是 BLE 扫描与连接的核心,依赖蓝牙与定位权限;SendBleDataThread负责向设备发送数据,可能涉及文件读写,因此依赖存储与网络权限。 HomeActivity是 Launcher 入口(android.intent.action.MAIN+LAUNCHER),CommonActivity是通用的交互页面,两者通过configChanges配置避免屏幕旋转时的重建问题,与权限声明相互独立。
权限声明清单(Manifest)
工程在 ATTConnect/app/src/main/AndroidManifest.xml 中集中声明了全部权限,源码如下:
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
Source: AndroidManifest.xml
经典蓝牙权限:BLUETOOTH / BLUETOOTH_ADMIN
BLUETOOTH(保护级别:normal):允许应用执行蓝牙通信,如请求连接、接受连接、传输数据。Android 11 及以下无需运行时申请。BLUETOOTH_ADMIN(保护级别:normal):允许应用发现设备并操作蓝牙设置,例如发起扫描、修改蓝牙状态。同样无需运行时申请。
设计意图:Android 12 之前系统对蓝牙权限不敏感,两个普通权限加上定位权限即可完成 BLE 开发;本工程保留这两个声明是为了兼容旧版本设备(旧系统会忽略未知的 BLUETOOTH_CONNECT/BLUETOOTH_SCAN 声明,而新系统不再要求这两个旧权限)。
Android 12+ 运行时权限:BLUETOOTH_SCAN / BLUETOOTH_CONNECT
BLUETOOTH_SCAN(保护级别:dangerous):允许执行 BLE 扫描。需要与定位权限配合使用(见下),否则扫描结果可能被过滤。Android 12+ 必须运行时申请。BLUETOOTH_CONNECT(保护级别:dangerous):允许连接已配对的蓝牙设备、执行通信。Android 12+ 必须运行时申请。- 若应用需要对外广播(Beacon 场景),还需
BLUETOOTH_ADVERTISE;本示例为主动连接型应用,未声明该权限。
设计意图:Android 12 将"扫描"与"连接"拆分为独立权限,使用户可以只授予其中一种能力,遵循最小权限原则。本工程同时声明两者,覆盖扫描 + 连接 + 数据收发的完整演示链路。
定位权限:ACCESS_COARSE_LOCATION / ACCESS_FINE_LOCATION
- 两者均为 dangerous 权限。BLE 扫描在 Android 11 及以下(以及部分 12+ 设备)依赖定位权限才能返回真实设备结果——这是蓝牙技术本身不暴露设备位置、但系统将 BLE 扫描视为隐私敏感操作的设计约束。
- 本工程同时声明粗/细定位权限,并可在运行时按需申请;
ACCESS_FINE_LOCATION的精度更高,可满足对扫描结果更严格的场景。
存储与网络权限
READ_EXTERNAL_STORAGE/WRITE_EXTERNAL_STORAGE:用于读取/写入设备端文件(例如向蓝牙设备推送固件或文件),配合FileProvider(Manifest 中androidx.core.content.FileProvider声明)在应用间安全共享文件。INTERNET:normal 权限,供网络相关功能使用,与蓝牙链路解耦。
按 Android 版本的权限模型
下表汇总了本工程涉及的权限在不同 Android 版本下的行为差异,是排查"能编译但扫不到设备"类问题的关键参考:
| 权限 | Android ≤ 11 (API ≤ 30) | Android 12+ (API 31+) | 是否需要运行时申请 |
|---|---|---|---|
BLUETOOTH | 普通权限,声明即生效 | 已废弃,被系统忽略 | 否 |
BLUETOOTH_ADMIN | 普通权限,声明即生效 | 已废弃,被系统忽略 | 否 |
BLUETOOTH_SCAN | 未知权限,被系统忽略 | dangerous,必须声明 | 是(12+) |
BLUETOOTH_CONNECT | 未知权限,被系统忽略 | dangerous,必须声明 | 是(12+) |
ACCESS_COARSE_LOCATION | dangerous,影响 BLE 扫描 | dangerous,影响 BLE 扫描 | 是 |
ACCESS_FINE_LOCATION | dangerous,影响 BLE 扫描 | dangerous,影响 BLE 扫描 | 是 |
READ/WRITE_EXTERNAL_STORAGE | dangerous(WRITE 在 ≤18 为普通) | 受分区存储限制 | 视场景而定 |
关键结论:
- 新旧权限同时声明是兼容多版本的正确做法——低版本系统忽略未知权限声明,高版本系统忽略废弃权限声明,互不冲突。
- Android 12+ 上仅声明权限还不够,
BLUETOOTH_SCAN与BLUETOOTH_CONNECT必须在运行时弹出系统对话框请求用户授权;拒绝后将抛出SecurityException。 - 定位权限是 BLE 扫描的隐性前提——即使 Android 12+ 已授予
BLUETOOTH_SCAN,若未授予定位权限,扫描回调仍可能拿不到设备或结果为空(取决于厂商实现)。
运行时权限申请流程
由于本仓库 Java 源码中暂未检索到 requestPermissions / checkSelfPermission 等运行时申请调用(本次源码探索范围内未能验证到申请代码),以下流程图基于 Manifest 声明所支持的 Android 标准运行时权限模型给出应用启动到 BLE 扫描的完整状态流转:
stateDiagram-v2
[*] --> 启动: HomeActivity onCreate
启动 --> 检查权限: 检查 BLUETOOTH_SCAN / CONNECT / 定位
检查权限 --> 已授权: 全部已授予
检查权限 --> 申请中: 存在未授权权限
申请中 --> 已授权: 用户全部允许
申请中 --> 部分拒绝: 用户拒绝部分/全部
部分拒绝 --> 提示说明: 展示权限用途说明
提示说明 --> 申请中: 用户再次尝试
提示说明 --> 降级运行: 用户坚持拒绝
已授权 --> BLE扫描: BleManager.startScan()
BLE扫描 --> 连接设备: 发现目标设备
连接设备 --> 数据收发: 连接成功
降级运行 --> 结束
数据收发 --> 结束
流程要点:
- 首次启动时应一次性请求"蓝牙扫描 + 蓝牙连接 + 定位"三类权限,避免用户被多次打断。
- 用户拒绝后,Android 会在下次请求时提供"不再询问"选项;此时应引导用户前往系统设置页手动开启,而不是反复弹窗。
- 权限授予后若用户在系统设置中关闭,应用进程会被系统回收,下次启动时重新走检查流程,因此每次使用蓝牙前都应重新校验,不能缓存授权状态。
典型请求时序
sequenceDiagram
participant U as 用户
participant A as App (Activity)
participant S as Android 系统
participant B as BleManager
A->>S: checkSelfPermission(BLUETOOTH_SCAN)
S-->>A: 未授权
A->>S: requestPermissions(SCAN, CONNECT, LOCATION)
S->>U: 显示权限对话框
U->>S: 允许
S-->>A: onRequestPermissionsResult(GRANTED)
A->>B: startScan()
B->>S: 系统蓝牙扫描 (依赖 BLUETOOTH_SCAN + 定位)
S-->>B: 扫描回调 (ScanDeviceInfo)
B-->>A: 设备列表更新
使用示例
示例 1:完整的 Manifest 权限配置
以下为工程实际使用的完整 Manifest 权限段(含 tools:targetApi="31" 的版本适配声明):
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<application
android:name=".MyApplication"
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.BluetoothDemo"
tools:targetApi="31">
<activity android:name=".ui.home.HomeActivity"
android:exported="true"
android:configChanges="keyboard|keyboardHidden|screenLayout|uiMode">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".ui.common.CommonActivity"
android:configChanges="keyboard|keyboardHidden|screenLayout|uiMode"
android:enabled="true"
android:exported="true"
android:screenOrientation="sensorPortrait" />
</application>
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
</manifest>
Source: AndroidManifest.xml
要点解读:
android:exported="true"仅用于 Launcher 入口HomeActivity与需要被外部调起的CommonActivity,符合 Android 12+ 对显式导出组件的强制要求。FileProvider声明(androidx.core.content.FileProvider)与WRITE_EXTERNAL_STORAGE配合,用于向蓝牙设备传输文件时生成安全的内容 URI。tools:targetApi="31"告知 lint 工具本应用面向 API 31 编译,避免对高版本权限 API 的误报。
示例 2:BLE 扫描回调的数据模型
权限授予后,BleManager 通过扫描回调返回设备信息,其数据模型定义如下(展示权限放行后下游代码如何消费扫描结果):
public class ScanDeviceInfo {
// BLE 扫描回调返回的设备信息封装
}
Source: ScanDeviceInfo.java
说明:本次源码探索范围内未能读取
ScanDeviceInfo的完整字段定义;运行时权限申请代码亦未在 Java 源码中检索到。若需补充完整 API 签名,可进一步查看BleManager.java中的扫描入口与IBtScanCallback接口。
配置选项
权限配置本质上是 Manifest 声明 + 运行时申请两部分的组合。本工程相关的可配置项如下:
| 配置项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
android.permission.BLUETOOTH | Manifest 声明 | 声明 | 经典蓝牙通信权限(≤11 生效) |
android.permission.BLUETOOTH_ADMIN | Manifest 声明 | 声明 | 经典蓝牙管理权限(≤11 生效) |
android.permission.BLUETOOTH_SCAN | Manifest 声明 | 声明 | Android 12+ 扫描权限(运行时申请) |
android.permission.BLUETOOTH_CONNECT | Manifest 声明 | 声明 | Android 12+ 连接权限(运行时申请) |
android.permission.ACCESS_COARSE_LOCATION | Manifest 声明 | 声明 | 粗定位,影响 BLE 扫描结果 |
android.permission.ACCESS_FINE_LOCATION | Manifest 声明 | 声明 | 精定位,影响 BLE 扫描结果 |
android.permission.READ/WRITE_EXTERNAL_STORAGE | Manifest 声明 | 声明 | 文件读写,配合 FileProvider 传输 |
tools:targetApi | Manifest 属性 | 31 | 声明目标 API,影响权限校验与 lint 行为 |
android:exported | 组件属性 | true(两个 Activity) | Android 12+ 强制要求显式声明 |
故障模式与边界情况
1. Android 12+ 未运行时申请新权限
- 症状:调用蓝牙 API 时抛出
SecurityException(如Need BLUETOOTH_CONNECT permission for ...)。 - 原因:
BLUETOOTH_SCAN/BLUETOOTH_CONNECT是 dangerous 权限,仅声明不够。 - 处理:在访问蓝牙前先执行
checkSelfPermission,未授权则requestPermissions;捕获SecurityException并给出引导提示。
2. BLE 扫描结果为空
- 症状:扫描回调始终无设备返回,或返回的设备列表不完整。
- 原因:未授予定位权限(尤其 Android 11 及以下),或系统位置服务(GPS/位置开关)未开启。
- 处理:确保同时授予
ACCESS_FINE_LOCATION并提示用户开启系统定位开关;Android 12+ 仍需保留定位权限声明以保证扫描结果完整。
3. 用户在系统设置中撤销权限
- 症状:应用被系统终止,下次启动时蓝牙功能失效。
- 原因:dangerous 权限可被用户在运行时随时撤销。
- 处理:每次进入扫描/连接页面时重新校验权限,不能依赖上次的授权结果(参考上文状态图中的"每次使用前重新校验"路径)。
4. "不再询问"(Don't ask again)分支
- 症状:
requestPermissions不再弹出系统对话框,直接返回拒绝。 - 处理:检测
shouldShowRequestPermissionRationale()返回 false 且未授权时,引导用户跳转系统设置页(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)手动开启。
5. 并发与回调时序
- 蓝牙 API 的系统回调(扫描回调、连接状态回调)运行在系统 binder 线程,不能直接更新 UI,需切换到主线程;本工程的
BleEventCallbackManager与SendBleDataThread即承担事件分发与线程协调职责。 - 多次快速调用
startScan可能触发系统Scan already started异常,扫描前应先停止上一次扫描(幂等处理)。
性能与运维建议
- 扫描功耗:BLE 扫描是高功耗操作,建议指定过滤条件(如目标服务 UUID,见工程中
UuidUtil工具)并设置扫描超时,避免持续全量扫描耗尽设备电量。 - 权限请求合并:将
BLUETOOTH_SCAN、BLUETOOTH_CONNECT、ACCESS_FINE_LOCATION合并为一次requestPermissions调用,减少用户被打断次数,提升授权通过率。 - 目标 API 升级:若后续将
targetSdkVersion提升到 31+,请同步确认运行时申请逻辑已实现,否则在 12+ 设备上会直接崩溃。
扩展点
BLUETOOTH_ADVERTISE:若应用需要作为 BLE 外设被其他设备发现(Beacon/广播场景),可在 Manifest 中追加该权限并在运行时申请。NEARBY_WIFI_DEVICES/UWB_RANGING:若应用同时使用 Wi-Fi 直连或超宽带测距,可参考蓝牙权限的分组申请模式一并处理。- 权限申请框架:工程可引入
ActivityResultContracts.RequestMultiplePermissions(AndroidX)或 PermissionX 等封装库,将运行时申请逻辑收敛到统一入口,便于多页面复用。
Related Links
- AndroidManifest.xml(权限声明源码)
- BleManager.java(BLE 扫描/连接核心)
- BleEventCallbackManager.java(蓝牙事件分发)
- BluetoothUtil.java(蓝牙工具类)
- 入门系列其他页面:BLE 扫描与连接流程(本目录下的关联主题页)
- Android 官方文档:蓝牙权限(Bluetooth permissions)