2. BLE 基础概念与 Android 权限处理

本章从 BLE 核心角色与 GATT 协议入手,梳理扫描、连接、服务发现的标准流程,然后聚焦 Android 平台下 BLUETOOTH、BLUETOOTH_ADMIN、ACCESS_FINE_LOCATION 等权限的声明与运行时处理,为后续集成 Hilt 管理 BLE 生命周期打下基础。

2.1 BLE 角色:Central 与 Peripheral

BLE 网络中设备分为两种角色:

  • Central(中心设备):发起扫描并主动建立连接的设备,如手机、平板。
  • Peripheral(外围设备):广播自身数据、等待被连接的设备,如智能手环、传感器标签。

一个设备可同时兼任两种角色(如 Android 设备作为 GATT Server 时同时可作为 Client),但典型场景下 Android 应用扮演 Central 角色。角色决定了扫描、连接、数据交互的操作方向。

角色典型设备行为
Central手机、平板、PC扫描广播 → 发起连接 → 发现服务 → 读写特征值
Peripheral传感器、手环、心率带广播自身信息 → 响应连接 → 暴露 GATT 服务

2.2 GATT 协议:服务与特征值

GATT(Generic Attribute Profile)定义了 BLE 连接后的数据交换方式。数据以嵌套结构组织:

  • 服务(Service):一组相关功能的集合,用 UUID 标识。
  • 特征值(Characteristic):服务内的具体数据点,包含值和属性(读/写/通知)。
  • 描述符(Descriptor):描述特征值的配置信息(如 CCCD 用于启用通知)。

Android 的 BluetoothGatt API 将外围设备的 GATT 数据暴露为 BluetoothGattService、BluetoothGattCharacteristic、BluetoothGattDescriptor 对象。

2.3 扫描、连接、服务发现流程

典型中央设备交互流程如下:

  1. 扫描:通过 BluetoothLeScanner 扫描外围广播包,获取 ScanResult(含设备地址、名称、RSSI)。
  2. 连接:使用 BluetoothDevice.connectGatt() 发起连接,返回 BluetoothGatt 实例。
  3. 服务发现:调用 BluetoothGatt.discoverServices(),成功后通过回调获取所有服务列表。
  4. 特征值操作:读、写或订阅通知。
扫描阶段必须停止后才能发起连接,否则可能导致连接失败或资源泄露。

2.4 Android 权限清单声明

从 Android 6.0(API 23)起,除蓝牙权限外,扫描 BLE 设备还需要位置权限。在 AndroidManifest.xml 中添加:

<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" android:maxSdkVersion="30" />
<!-- Android 10+ 可选 ACCESS_BACKGROUND_LOCATION 用于后台扫描 -->
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />

从 Android 12(API 31)起,新增 BLUETOOTH_SCAN、BLUETOOTH_CONNECT、BLUETOOTH_ADVERTISE 权限,需视角色声明:

<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />

2.5 运行时权限请求(示例)

扫描前必须获取用户授权,典型代码片段(Kotlin):

val permissions = when {
    Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
        arrayOf(
            Manifest.permission.BLUETOOTH_SCAN,
            Manifest.permission.BLUETOOTH_CONNECT
        )
    }
    Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q -> {
        arrayOf(
            Manifest.permission.ACCESS_FINE_LOCATION,
            Manifest.permission.ACCESS_BACKGROUND_LOCATION
        )
    }
    else -> {
        arrayOf(Manifest.permission.ACCESS_FINE_LOCATION)
    }
}

if (permissions.all { ContextCompat.checkSelfPermission(this, it) == PackageManager.PERMISSION_GRANTED }) {
    startBleScan()
} else {
    ActivityResultContracts.RequestMultiplePermissions().launch(permissions)
}
⚠️ 从 Android 12 起,扫描权限与连接权限分离,若只声明 ACCESS_FINE_LOCATION 而不声明 BLUETOOTH_SCAN 会导致扫描失败。必须按 API 级别做条件适配。

2.6 权限与 BLE 操作的关系

操作所需权限(API 23‑30)所需权限(API 31+)
BLE 扫描ACCESS_FINE_LOCATIONBLUETOOTH_SCAN + (+ 若需过滤位置)
建立连接BLUETOOTH_ADMINBLUETOOTH_CONNECT
广播/发布服务BLUETOOTH_ADMINBLUETOOTH_ADVERTISE
读/写特征值BLUETOOTHBLUETOOTH_CONNECT

2.7 内联 SVG:BLE 权限与角色关系图

BLE 角色与权限映射流程 Central (手机) 发起扫描/连接 Peripheral (传感器) 广播/响应连接 广播包 GATT 连接 Central 所需权限 • BLUETOOTH_SCAN (API 31+) • BLUETOOTH_CONNECT (API 31+) • ACCESS_FINE_LOCATION (API 23‑30) Peripheral 所需权限 • BLUETOOTH_ADVERTISE (API 31+) • BLUETOOTH_CONNECT • 无需位置权限 GATT 数据组织 服务 → 特征值 → 描述符

2.8 常见错误与调试验证

  • 扫描无结果:检查 ACCESS_FINE_LOCATION 是否已授权,位置服务是否打开,BLE 外设是否在范围内。
  • 连接立即断开:通常出现在未声明 BLUETOOTH_CONNECT(API 31+)或 GATT 回调未正确处理时。
  • SecurityException:未在运行时请求 BLUETOOTH_SCAN 或 BLUETOOTH_CONNECT。调试时查看 logcat 关键字 SecurityException。
  • 服务列表为空:调用 discoverServices() 后未等待 onServicesDiscovered 回调,或外围设备未正确暴露服务。
使用 Android 提供的 BluetoothAdapter 的 isEnabled() 方法判断蓝牙是否开启,并在权限请求流程前引导用户开启蓝牙。

2.9 稳定性优化建议

  • 扫描回调使用 ScanSettings 设置合理扫描模式(低功耗 vs 低延迟),避免高功耗导致系统杀进程。
  • 连接时使用 TRANSPORT_LE 明确指定 BLE 传输,避免兼容性问题。
  • 在 onConnectionStateChange 回调中正确处理断线重连逻辑,避免重连风暴。
  • 权限请求回调内使用 shouldShowRequestPermissionRationale 处理用户拒绝后引导。