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 扫描、连接、服务发现流程
典型中央设备交互流程如下:
- 扫描:通过
BluetoothLeScanner扫描外围广播包,获取ScanResult(含设备地址、名称、RSSI)。 - 连接:使用
BluetoothDevice.connectGatt()发起连接,返回BluetoothGatt实例。 - 服务发现:调用
BluetoothGatt.discoverServices(),成功后通过回调获取所有服务列表。 - 特征值操作:读、写或订阅通知。
扫描阶段必须停止后才能发起连接,否则可能导致连接失败或资源泄露。
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_LOCATION | BLUETOOTH_SCAN + (+ 若需过滤位置) |
| 建立连接 | BLUETOOTH_ADMIN | BLUETOOTH_CONNECT |
| 广播/发布服务 | BLUETOOTH_ADMIN | BLUETOOTH_ADVERTISE |
| 读/写特征值 | BLUETOOTH | BLUETOOTH_CONNECT |
2.7 内联 SVG:BLE 权限与角色关系图
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处理用户拒绝后引导。