杰理 SDK 文档中心
首页
首页
  • 项目概览

    • 仓库简介与示例构成
    • 支持的平台与协议
  • 快速开始

    • 导入工程与编译运行
    • 蓝牙权限配置
  • 应用架构

    • 工程结构与模块分层
    • 核心类与回调接口
  • 核心功能

    • 蓝牙设备扫描
    • ATT 设备连接与断开管理
    • 数据收发与通知回调
  • 配置与调试

    • 协议配置常量
    • 日志系统与调试指南
  • 界面与交互

    • 设备扫描与连接界面
    • 设备详情与设置界面

蓝牙权限配置

本页介绍 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_LOCATIONdangerous,影响 BLE 扫描dangerous,影响 BLE 扫描是
ACCESS_FINE_LOCATIONdangerous,影响 BLE 扫描dangerous,影响 BLE 扫描是
READ/WRITE_EXTERNAL_STORAGEdangerous(WRITE 在 ≤18 为普通)受分区存储限制视场景而定

关键结论:

  1. 新旧权限同时声明是兼容多版本的正确做法——低版本系统忽略未知权限声明,高版本系统忽略废弃权限声明,互不冲突。
  2. Android 12+ 上仅声明权限还不够,BLUETOOTH_SCAN 与 BLUETOOTH_CONNECT 必须在运行时弹出系统对话框请求用户授权;拒绝后将抛出 SecurityException。
  3. 定位权限是 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.BLUETOOTHManifest 声明声明经典蓝牙通信权限(≤11 生效)
android.permission.BLUETOOTH_ADMINManifest 声明声明经典蓝牙管理权限(≤11 生效)
android.permission.BLUETOOTH_SCANManifest 声明声明Android 12+ 扫描权限(运行时申请)
android.permission.BLUETOOTH_CONNECTManifest 声明声明Android 12+ 连接权限(运行时申请)
android.permission.ACCESS_COARSE_LOCATIONManifest 声明声明粗定位,影响 BLE 扫描结果
android.permission.ACCESS_FINE_LOCATIONManifest 声明声明精定位,影响 BLE 扫描结果
android.permission.READ/WRITE_EXTERNAL_STORAGEManifest 声明声明文件读写,配合 FileProvider 传输
tools:targetApiManifest 属性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)
Prev
导入工程与编译运行