蓝牙BLE通信开发完全指南
蓝牙BLE通信开发完全指南
目录
一句话总结
BLE(低功耗蓝牙)是专为物联网设备设计的短距离无线通信技术,通过GATT协议实现低功耗、低成本的数据传输,广泛应用于智能穿戴、医疗健康、智能门锁等领域。
核心架构图
BLE通信架构全景
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
智能手机/平板(中心设备 Central)
┌─────────────────────────────┐
│ BLE应用层 (App) │
├─────────────────────────────┤
│ GATT Client │
│ - 读取特征值 │
│ - 写入特征值 │
│ - 订阅通知 │
├─────────────────────────────┤
│ GAP (连接管理) │
│ - 扫描设备 │
│ - 建立连接 │
│ - 断开连接 │
├─────────────────────────────┤
│ Link Layer (链路层) │
├─────────────────────────────┤
│ Physical Layer (物理层) │
└─────────────────────────────┘
↕ 无线通信
┌─────────────────────────────┐
│ Physical Layer (物理层) │
├─────────────────────────────┤
│ Link Layer (链路层) │
├─────────────────────────────┤
│ GAP (广播管理) │
│ - 发送广播 │
│ - 响应连接请求 │
├─────────────────────────────┤
│ GATT Server │
│ ┌───────────────────┐ │
│ │ Service 1 │ │
│ │ ├─ Characteristic │ │
│ │ └─ Characteristic │ │
│ ├───────────────────┤ │
│ │ Service 2 │ │
│ │ ├─ Characteristic │ │
│ │ └─ Characteristic │ │
│ └───────────────────┘ │
├─────────────────────────────┤
│ 应用逻辑 │
└─────────────────────────────┘
智能手环/门锁/传感器(外围设备 Peripheral)
关键参数:
- 工作频段:2.4GHz ISM频段(2400-2483.5MHz)
- 传输距离:10-100米(取决于功率等级)
- 传输速率:1Mbps(BLE 4.x),2Mbps(BLE 5.0)
- 功耗:< 15mA(活跃),< 1μA(休眠)
- 连接延迟:6ms起(连接间隔可配置)
一、BLE基础概念
1.1 什么是BLE?
BLE(Bluetooth Low Energy,低功耗蓝牙)是蓝牙4.0规范的一部分,专为物联网设备设计的短距离无线通信技术。与传统蓝牙(Classic Bluetooth)不同,BLE专注于低功耗、低成本、低复杂度的应用场景。
核心特性:
- 超低功耗:峰值功耗<15mA,休眠功耗<1μA,纽扣电池可运行数月至数年
- 快速连接:连接建立时间<6ms,远快于经典蓝牙的数秒
- 灵活通信:支持广播、连接两种模式,适应不同应用场景
- 广泛兼容:iOS、Android、Windows、Linux全平台支持
1.2 BLE vs 经典蓝牙
| 对比维度 | BLE(低功耗蓝牙) | 经典蓝牙 | 适用场景 |
|---|---|---|---|
| 功耗 | < 15mA活跃,< 1μA休眠 | 30-100mA | BLE:传感器、穿戴 经典:音频、大文件 |
| 传输速率 | 1Mbps(BLE 4.x) 2Mbps(BLE 5.0) |
2-3Mbps | BLE:遥测数据 经典:音频流 |
| 连接延迟 | 6ms起 | 100ms+ | BLE:实时交互 经典:稳定传输 |
| 传输距离 | 10-100m | 10-100m | 相同 |
| 数据包大小 | 20-512字节 | 无限制 | BLE:小数据 经典:大数据 |
| 应用场景 | 传感器、穿戴、门锁 | 音频、车载、文件传输 | - |
重要提示:BLE 4.x和5.0不兼容经典蓝牙协议栈,它们是完全独立的技术。
1.3 BLE的演进历史
BLE技术演进时间线
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
2010年 - Bluetooth 4.0
└─ 引入BLE技术,传输速率1Mbps
└─ iOS 5/Android 4.3开始支持
2013年 - Bluetooth 4.1
└─ 改进连接管理,支持设备同时作为中心和外围
└─ IPv6支持,为IoT铺路
2014年 - Bluetooth 4.2
└─ 数据包长度扩展(20→255字节)
└─ 隐私保护增强(地址随机化)
2016年 - Bluetooth 5.0 ⭐重大升级
└─ 传输速率翻倍(2Mbps)
└─ 传输距离4倍提升(400m室外)
└─ 广播容量8倍提升(255字节)
2019年 - Bluetooth 5.1
└─ 方向查找功能(AoA/AoD)
└─ 室内定位精度<1m
2020年 - Bluetooth 5.2
└─ LE Audio音频标准
└─ 等时通道(Isochronous Channels)
2021年 - Bluetooth 5.3
└─ 连接更新增强
└─ 广播加密
1.4 BLE应用场景
典型应用领域:
-
智能穿戴设备
- 智能手环/手表:心率监测、步数统计、消息提醒
- 运动传感器:骑行功率计、跑步足传感器
- 耳机:TWS真无线耳机(BLE音频)
-
医疗健康设备
- 血压计、血糖仪:数据同步到手机App
- 体温计、体脂秤:健康数据管理
- 助听器:BLE音频传输
-
智能家居设备
- 智能门锁:蓝牙钥匙、开门记录
- 温湿度传感器:环境监测
- 智能灯泡:调光调色控制
-
资产追踪与定位
- AirTag/防丢器:蓝牙信标定位
- 仓储管理:物品位置追踪
- 人员定位:室内导航
-
零售与营销
- iBeacon:店内导航、优惠推送
- 电子价签:动态价格更新
- 无人售货机:支付与交互
二、GATT协议深度解析
2.1 GATT协议概述
GATT(Generic Attribute Profile,通用属性协议)是BLE通信的核心协议,定义了数据的组织方式和访问方法。GATT基于ATT(Attribute Protocol)实现,提供了层次化的数据结构。
GATT层次结构:
GATT数据结构
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Profile(配置文件)
└─ Service(服务) [UUID: 0x180D - 心率服务]
├─ Characteristic(特征值) [UUID: 0x2A37 - 心率测量]
│ ├─ Value(值):实际数据(如心率BPM)
│ ├─ Properties(属性):Read/Write/Notify/Indicate
│ └─ Descriptor(描述符) [UUID: 0x2902 - CCCD客户端特征配置]
│ └─ Value:启用/禁用通知
│
└─ Characteristic [UUID: 0x2A38 - 身体传感器位置]
├─ Value:手腕/胸部/手指等
└─ Properties:Read
实例:心率传感器GATT结构
┌──────────────────────────────────────────┐
│ Heart Rate Service (0x180D) │
│ ├─ Heart Rate Measurement (0x2A37) │
│ │ - Properties: Notify │
│ │ - Value: [心率值: 78 BPM] │
│ │ - Descriptor: CCCD (0x2902) │
│ │ └─ Value: 0x0001 (通知已启用) │
│ │ │
│ └─ Body Sensor Location (0x2A38) │
│ - Properties: Read │
│ - Value: 0x01 (胸部) │
│ │
│ Battery Service (0x180F) │
│ └─ Battery Level (0x2A19) │
│ - Properties: Read, Notify │
│ - Value: 85% │
└──────────────────────────────────────────┘
2.2 Service(服务)
Service是一组相关特征值的集合,每个Service由唯一的UUID标识。
Service类型:
-
标准Service(SIG定义):
- 心率服务(Heart Rate Service, 0x180D)
- 电池服务(Battery Service, 0x180F)
- 设备信息服务(Device Information Service, 0x180A)
- 完整列表:Bluetooth SIG Services
-
自定义Service:
- 使用128位UUID(如:
0000FFF0-0000-1000-8000-00805F9B34FB) - 适用于专有功能
- 使用128位UUID(如:
Service声明示例(Android BLE Server):
val heartRateService = BluetoothGattService(
UUID.fromString("0000180D-0000-1000-8000-00805F9B34FB"), // 心率服务UUID
BluetoothGattService.SERVICE_TYPE_PRIMARY
)
2.3 Characteristic(特征值)
Characteristic是GATT中最小的数据单元,包含一个值和若干属性。
Characteristic属性(Properties):
| 属性 | 说明 | 典型应用 |
|---|---|---|
| Read | 客户端可读取值 | 电池电量、传感器状态 |
| Write | 客户端可写入值 | LED开关、设备配置 |
| Write Without Response | 写入无需响应(快速) | 游戏手柄按键 |
| Notify | 服务端主动推送(无需确认) | 心率数据、温度监测 |
| Indicate | 服务端主动推送(需确认) | 关键告警信息 |
| Broadcast | 广播模式发送 | 信标定位 |
Characteristic声明示例:
val heartRateMeasurement = BluetoothGattCharacteristic(
UUID.fromString("00002A37-0000-1000-8000-00805F9B34FB"), // 心率测量UUID
BluetoothGattCharacteristic.PROPERTY_NOTIFY, // 支持通知
BluetoothGattCharacteristic.PERMISSION_READ // 允许读取
)
// 添加CCCD描述符(用于启用通知)
val cccdDescriptor = BluetoothGattDescriptor(
UUID.fromString("00002902-0000-1000-8000-00805F9B34FB"), // CCCD UUID
BluetoothGattDescriptor.PERMISSION_READ or BluetoothGattDescriptor.PERMISSION_WRITE
)
heartRateMeasurement.addDescriptor(cccdDescriptor)
heartRateService.addCharacteristic(heartRateMeasurement)
2.4 Descriptor(描述符)
Descriptor是描述Characteristic的元数据,最常用的是CCCD(Client Characteristic Configuration Descriptor)。
常用Descriptor类型:
| UUID | 名称 | 作用 |
|---|---|---|
| 0x2902 | CCCD | 启用/禁用Notify/Indicate |
| 0x2900 | Characteristic Extended Properties | 扩展属性 |
| 0x2901 | Characteristic User Description | 用户描述 |
| 0x2904 | Characteristic Presentation Format | 数据格式(单位、精度) |
CCCD值说明:
CCCD配置值
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
0x0000 → 禁用通知和指示
0x0001 → 启用通知(Notification)
0x0002 → 启用指示(Indication)
0x0003 → 同时启用通知和指示
三、BLE连接流程详解
3.1 完整连接流程
BLE连接流程(从扫描到数据传输)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
阶段1:设备发现(Device Discovery)
┌────────────────────────────────────────────────┐
│ Peripheral(外围设备) │
│ └─ 发送广播包(Advertising Packet) │
│ - 设备名称 │
│ - Service UUID列表 │
│ - Tx功率等级 │
│ ↓ 每20ms-10s发送一次(可配置) │
│ │
│ Central(中心设备) │
│ └─ 扫描广播包(Scanning) │
│ - 被动扫描:仅监听 │
│ - 主动扫描:发送Scan Request获取更多信息 │
└────────────────────────────────────────────────┘
↓
阶段2:建立连接(Connection Establishment)
┌────────────────────────────────────────────────┐
│ Central 发送连接请求(Connect Request) │
│ ↓ │
│ Peripheral 停止广播,接受连接 │
│ ↓ │
│ 协商连接参数: │
│ - 连接间隔(Connection Interval):7.5ms-4s │
│ - 从机延迟(Slave Latency):0-499个间隔 │
│ - 超时时间(Supervision Timeout):100ms-32s │
└────────────────────────────────────────────────┘
↓
阶段3:服务发现(Service Discovery)
┌────────────────────────────────────────────────┐
│ Central 请求发现服务(Discover Services) │
│ ↓ │
│ Peripheral 返回所有Service列表 │
│ ↓ │
│ Central 请求发现特征值(Discover Characteristics)│
│ ↓ │
│ Peripheral 返回Characteristic列表及属性 │
│ ↓ │
│ Central 读取Descriptor(可选) │
└────────────────────────────────────────────────┘
↓
阶段4:数据传输(Data Transfer)
┌────────────────────────────────────────────────┐
│ ◆ 读取(Read) │
│ Central → Peripheral: Read Request │
│ Peripheral → Central: Read Response(数据) │
│ │
│ ◆ 写入(Write) │
│ Central → Peripheral: Write Request + 数据 │
│ Peripheral → Central: Write Response(确认) │
│ │
│ ◆ 通知(Notify) │
│ 1. Central 写入CCCD启用通知(0x0001) │
│ 2. Peripheral 定期发送Notification(无需确认) │
│ │
│ ◆ 指示(Indicate) │
│ 1. Central 写入CCCD启用指示(0x0002) │
│ 2. Peripheral 发送Indication │
│ 3. Central 回复Confirmation(确认) │
└────────────────────────────────────────────────┘
↓
阶段5:断开连接(Disconnection)
┌────────────────────────────────────────────────┐
│ Central 或 Peripheral 发送断开请求 │
│ ↓ │
│ 连接终止,Peripheral 恢复广播状态 │
└────────────────────────────────────────────────┘
3.2 连接参数详解
连接间隔(Connection Interval):
- 定义:Central多久轮询一次Peripheral
- 范围:7.5ms - 4s
- 影响:
- 小间隔(如7.5ms):低延迟,高功耗
- 大间隔(如1s):高延迟,低功耗
- 典型值:
- 实时控制(游戏手柄):20-40ms
- 数据同步(健康设备):100-200ms
- 低功耗监控(传感器):1-2s
从机延迟(Slave Latency):
- 定义:Peripheral可跳过的连接事件数(不响应轮询)
- 范围:0-499
- 作用:在无数据传输时节省功耗
- 计算:实际唤醒间隔 = 连接间隔 × (1 + 从机延迟)
示例:
连接间隔:100ms
从机延迟:4
→ Peripheral每500ms唤醒一次(100ms × 5)
→ 功耗降低80%
超时时间(Supervision Timeout):
- 定义:多久未收到对方数据包则判定连接断开
- 范围:100ms - 32s
- 约束:必须 > (1 + Slave Latency) × Connection Interval × 2
3.3 广播包结构
广播包(Advertising Packet)格式:
BLE广播包结构(最大31字节)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
┌─────────────────────────────────────────────────┐
│ Preamble (1 byte) │ 前导码 │
├─────────────────────────┼───────────────────────┤
│ Access Address (4 bytes)│ 访问地址 │
├─────────────────────────┼───────────────────────┤
│ PDU Header (2 bytes) │ PDU头部 │
├─────────────────────────┼───────────────────────┤
│ MAC Address (6 bytes) │ 设备MAC地址 │
├─────────────────────────┼───────────────────────┤
│ Advertising Data (0-31) │ 广播数据 │
│ ┌──────────────────────────────────────┐ │
│ │ AD Structure 1 │ │
│ │ ├─ Length (1 byte) │ │
│ │ ├─ Type (1 byte) → 0x09 (完整设备名) │ │
│ │ └─ Data (N bytes) → "MyDevice" │ │
│ ├──────────────────────────────────────┤ │
│ │ AD Structure 2 │ │
│ │ ├─ Length (1 byte) │ │
│ │ ├─ Type (1 byte) → 0x07 (128位UUID) │ │
│ │ └─ Data (16 bytes) → Service UUID │ │
│ └──────────────────────────────────────┘ │
├─────────────────────────┼───────────────────────┤
│ CRC (3 bytes) │ 校验码 │
└─────────────────────────┴───────────────────────┘
常用AD Type:
0x01 - Flags(设备角色标志)
0x02 - 部分16位Service UUID列表
0x03 - 完整16位Service UUID列表
0x06 - 部分128位Service UUID列表
0x07 - 完整128位Service UUID列表
0x08 - 缩短设备名
0x09 - 完整设备名
0x0A - Tx功率等级
0xFF - 厂商自定义数据
广播包示例(智能手环):
// 原始广播包(十六进制)
val advPacket = byteArrayOf(
0x02, 0x01, 0x06, // Flags: 一般可发现模式,仅支持BLE
0x03, 0x03, 0x0D, 0x18, // 16位Service UUID: 0x180D(心率服务)
0x0A, 0x09, 0x4D, 0x79, // 完整设备名: "MyBand2024"
0x42, 0x61, 0x6E, 0x64,
0x32, 0x30, 0x32, 0x34
)
四、Android BLE开发实战
4.1 权限申请
所需权限(AndroidManifest.xml):
<!-- 蓝牙基础权限 -->
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<!-- Android 12+ 新权限 -->
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" /> <!-- 仅广播设备需要 -->
<!-- 位置权限(扫描BLE设备需要) -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<!-- 声明BLE特性 -->
<uses-feature android:name="android.hardware.bluetooth_le" android:required="true" />
运行时权限申请:
class BleActivity : AppCompatActivity() {
private val requiredPermissions = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
arrayOf(
Manifest.permission.BLUETOOTH_SCAN,
Manifest.permission.BLUETOOTH_CONNECT,
Manifest.permission.ACCESS_FINE_LOCATION
)
} else {
arrayOf(
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.BLUETOOTH,
Manifest.permission.BLUETOOTH_ADMIN
)
}
private val permissionLauncher = registerForActivityResult(
ActivityResultContracts.RequestMultiplePermissions()
) { permissions ->
if (permissions.all { it.value }) {
startBleScan()
} else {
Toast.makeText(this, "权限被拒绝", Toast.LENGTH_SHORT).show()
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
permissionLauncher.launch(requiredPermissions)
}
}
4.2 扫描BLE设备
扫描实现(完整代码):
class BleScanner(private val context: Context) {
private val bluetoothAdapter: BluetoothAdapter? by lazy {
(context.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager).adapter
}
private val bleScanner: BluetoothLeScanner? by lazy {
bluetoothAdapter?.bluetoothLeScanner
}
private val scanResults = mutableMapOf<String, BluetoothDevice>()
private var isScanning = false
// 扫描回调
private val scanCallback = object : ScanCallback() {
override fun onScanResult(callbackType: Int, result: ScanResult) {
val device = result.device
val rssi = result.rssi
val scanRecord = result.scanRecord
// 解析广播包
val deviceName = scanRecord?.deviceName ?: "Unknown"
val serviceUuids = scanRecord?.serviceUuids?.map { it.toString() } ?: emptyList()
Log.d("BleScanner", """
发现设备:
- 名称: $deviceName
- 地址: ${device.address}
- RSSI: $rssi dBm
- Services: $serviceUuids
""".trimIndent())
scanResults[device.address] = device
onDeviceFound?.invoke(device, rssi)
}
override fun onScanFailed(errorCode: Int) {
Log.e("BleScanner", "扫描失败: $errorCode")
isScanning = false
onScanError?.invoke(errorCode)
}
}
// 回调接口
var onDeviceFound: ((BluetoothDevice, Int) -> Unit)? = null
var onScanError: ((Int) -> Unit)? = null
/**
* 开始扫描(带过滤器)
*/
@SuppressLint("MissingPermission")
fun startScan(
serviceUuid: UUID? = null,
deviceName: String? = null,
timeoutMs: Long = 10000 // 10秒超时
) {
if (isScanning) {
Log.w("BleScanner", "扫描已在进行中")
return
}
// 构建扫描过滤器
val filters = mutableListOf<ScanFilter>()
serviceUuid?.let {
filters.add(ScanFilter.Builder().setServiceUuid(ParcelUuid(it)).build())
}
deviceName?.let {
filters.add(ScanFilter.Builder().setDeviceName(it).build())
}
// 扫描设置
val scanSettings = ScanSettings.Builder()
.setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY) // 低延迟模式
.setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES)
.setMatchMode(ScanSettings.MATCH_MODE_AGGRESSIVE) // 积极匹配
.build()
// 开始扫描
scanResults.clear()
isScanning = true
bleScanner?.startScan(filters, scanSettings, scanCallback)
Log.d("BleScanner", "开始扫描BLE设备...")
// 自动停止扫描
Handler(Looper.getMainLooper()).postDelayed({
if (isScanning) stopScan()
}, timeoutMs)
}
/**
* 停止扫描
*/
@SuppressLint("MissingPermission")
fun stopScan() {
if (!isScanning) return
bleScanner?.stopScan(scanCallback)
isScanning = false
Log.d("BleScanner", "停止扫描,共发现 ${scanResults.size} 个设备")
}
}
4.3 连接与通信
完整GATT客户端实现:
class BleClient(private val context: Context, private val device: BluetoothDevice) {
private var bluetoothGatt: BluetoothGatt? = null
private val serviceMap = mutableMapOf<UUID, BluetoothGattService>()
// 连接状态回调
var onConnectionStateChange: ((Int) -> Unit)? = null
var onServicesDiscovered: ((List<BluetoothGattService>) -> Unit)? = null
var onCharacteristicRead: ((UUID, ByteArray) -> Unit)? = null
var onCharacteristicChanged: ((UUID, ByteArray) -> Unit)? = null
private val gattCallback = object : BluetoothGattCallback() {
@SuppressLint("MissingPermission")
override fun onConnectionStateChange(gatt: BluetoothGatt, status: Int, newState: Int) {
when (newState) {
BluetoothProfile.STATE_CONNECTED -> {
Log.d("BleClient", "已连接到 ${device.address}")
onConnectionStateChange?.invoke(newState)
// 发现服务
Handler(Looper.getMainLooper()).postDelayed({
gatt.discoverServices()
}, 600) // 等待600ms再发现服务(兼容性优化)
}
BluetoothProfile.STATE_DISCONNECTED -> {
Log.d("BleClient", "已断开连接")
onConnectionStateChange?.invoke(newState)
bluetoothGatt?.close()
bluetoothGatt = null
}
}
}
override fun onServicesDiscovered(gatt: BluetoothGatt, status: Int) {
if (status == BluetoothGatt.GATT_SUCCESS) {
val services = gatt.services
services.forEach { service ->
serviceMap[service.uuid] = service
Log.d("BleClient", "发现服务: ${service.uuid}")
service.characteristics.forEach { char ->
Log.d("BleClient", " - 特征值: ${char.uuid}, 属性: ${char.properties}")
}
}
onServicesDiscovered?.invoke(services)
} else {
Log.e("BleClient", "服务发现失败: $status")
}
}
override fun onCharacteristicRead(
gatt: BluetoothGatt,
characteristic: BluetoothGattCharacteristic,
status: Int
) {
if (status == BluetoothGatt.GATT_SUCCESS) {
val data = characteristic.value
Log.d("BleClient", "读取特征值: ${characteristic.uuid}, 数据: ${data.toHexString()}")
onCharacteristicRead?.invoke(characteristic.uuid, data)
}
}
override fun onCharacteristicWrite(
gatt: BluetoothGatt,
characteristic: BluetoothGattCharacteristic,
status: Int
) {
if (status == BluetoothGatt.GATT_SUCCESS) {
Log.d("BleClient", "写入成功: ${characteristic.uuid}")
} else {
Log.e("BleClient", "写入失败: $status")
}
}
override fun onCharacteristicChanged(
gatt: BluetoothGatt,
characteristic: BluetoothGattCharacteristic
) {
val data = characteristic.value
Log.d("BleClient", "收到通知: ${characteristic.uuid}, 数据: ${data.toHexString()}")
onCharacteristicChanged?.invoke(characteristic.uuid, data)
}
}
/**
* 连接设备
*/
@SuppressLint("MissingPermission")
fun connect() {
bluetoothGatt = device.connectGatt(context, false, gattCallback, BluetoothDevice.TRANSPORT_LE)
}
/**
* 断开连接
*/
@SuppressLint("MissingPermission")
fun disconnect() {
bluetoothGatt?.disconnect()
}
/**
* 读取特征值
*/
@SuppressLint("MissingPermission")
fun readCharacteristic(serviceUuid: UUID, characteristicUuid: UUID): Boolean {
val characteristic = serviceMap[serviceUuid]
?.getCharacteristic(characteristicUuid) ?: return false
return bluetoothGatt?.readCharacteristic(characteristic) == true
}
/**
* 写入特征值
*/
@SuppressLint("MissingPermission")
fun writeCharacteristic(serviceUuid: UUID, characteristicUuid: UUID, data: ByteArray): Boolean {
val characteristic = serviceMap[serviceUuid]
?.getCharacteristic(characteristicUuid) ?: return false
characteristic.value = data
characteristic.writeType = BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT
return bluetoothGatt?.writeCharacteristic(characteristic) == true
}
/**
* 启用通知
*/
@SuppressLint("MissingPermission")
fun enableNotification(serviceUuid: UUID, characteristicUuid: UUID): Boolean {
val characteristic = serviceMap[serviceUuid]
?.getCharacteristic(characteristicUuid) ?: return false
// 启用本地通知
val success = bluetoothGatt?.setCharacteristicNotification(characteristic, true) == true
if (!success) return false
// 写入CCCD启用远程通知
val cccdUuid = UUID.fromString("00002902-0000-1000-8000-00805F9B34FB")
val descriptor = characteristic.getDescriptor(cccdUuid) ?: return false
descriptor.value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE
return bluetoothGatt?.writeDescriptor(descriptor) == true
}
// 工具函数:字节数组转十六进制字符串
private fun ByteArray.toHexString() = joinToString("") { "%02X".format(it) }
}
4.4 使用示例
完整使用流程:
class MainActivity : AppCompatActivity() {
private lateinit var bleScanner: BleScanner
private var bleClient: BleClient? = null
// 心率服务UUID
private val HEART_RATE_SERVICE_UUID = UUID.fromString("0000180D-0000-1000-8000-00805F9B34FB")
private val HEART_RATE_MEASUREMENT_UUID = UUID.fromString("00002A37-0000-1000-8000-00805F9B34FB")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 初始化扫描器
bleScanner = BleScanner(this).apply {
onDeviceFound = { device, rssi ->
runOnUiThread {
addDeviceToList(device, rssi)
}
}
}
// 开始扫描
btnScan.setOnClickListener {
bleScanner.startScan(serviceUuid = HEART_RATE_SERVICE_UUID)
}
// 连接设备
btnConnect.setOnClickListener {
val device = getSelectedDevice() // 从列表获取选中设备
connectToDevice(device)
}
}
private fun connectToDevice(device: BluetoothDevice) {
bleClient = BleClient(this, device).apply {
// 监听连接状态
onConnectionStateChange = { state ->
runOnUiThread {
when (state) {
BluetoothProfile.STATE_CONNECTED -> {
tvStatus.text = "已连接"
}
BluetoothProfile.STATE_DISCONNECTED -> {
tvStatus.text = "已断开"
}
}
}
}
// 监听服务发现
onServicesDiscovered = { services ->
runOnUiThread {
tvStatus.text = "发现 ${services.size} 个服务"
// 启用心率通知
enableNotification(HEART_RATE_SERVICE_UUID, HEART_RATE_MEASUREMENT_UUID)
}
}
// 监听心率数据
onCharacteristicChanged = { uuid, data ->
if (uuid == HEART_RATE_MEASUREMENT_UUID) {
val heartRate = parseHeartRate(data)
runOnUiThread {
tvHeartRate.text = "$heartRate BPM"
}
}
}
connect()
}
}
// 解析心率数据(符合Bluetooth SIG标准)
private fun parseHeartRate(data: ByteArray): Int {
val flag = data[0].toInt()
return if ((flag and 0x01) == 0) {
// 8位心率值
data[1].toInt() and 0xFF
} else {
// 16位心率值
((data[2].toInt() and 0xFF) shl 8) or (data[1].toInt() and 0xFF)
}
}
override fun onDestroy() {
super.onDestroy()
bleScanner.stopScan()
bleClient?.disconnect()
}
}
五、行业应用案例分析
案例1:智能门锁蓝牙开锁系统
项目背景:
某智能家居厂商的智能门锁产品需要实现蓝牙开锁功能,替代传统钥匙。
技术方案:
智能门锁BLE架构
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
手机App(GATT Client)
│
├─ 蓝牙握手认证
│ └─ 交换加密密钥(AES-128)
│
├─ 开锁指令
│ └─ 写入特征值:{cmd: "unlock", token: "xxx"}
│
└─ 开锁日志
└─ 读取特征值:{time: "2024-02-24 10:30", user: "张三"}
↓ BLE通信(加密)
智能门锁(GATT Server)
│
├─ 认证服务(Custom Service)
│ └─ 密钥交换特征值
│
├─ 锁控制服务
│ ├─ 开锁特征值(Write)
│ └─ 锁状态特征值(Notify)
│
└─ 日志服务
└─ 开锁记录特征值(Read)
GATT服务定义(门锁端):
// 门锁控制服务
val lockService = BluetoothGattService(
UUID.fromString("0000FFF0-0000-1000-8000-00805F9B34FB"),
BluetoothGattService.SERVICE_TYPE_PRIMARY
)
// 开锁特征值
val unlockCharacteristic = BluetoothGattCharacteristic(
UUID.fromString("0000FFF1-0000-1000-8000-00805F9B34FB"),
BluetoothGattCharacteristic.PROPERTY_WRITE,
BluetoothGattCharacteristic.PERMISSION_WRITE
)
// 锁状态特征值
val lockStateCharacteristic = BluetoothGattCharacteristic(
UUID.fromString("0000FFF2-0000-1000-8000-00805F9B34FB"),
BluetoothGattCharacteristic.PROPERTY_NOTIFY,
BluetoothGattCharacteristic.PERMISSION_READ
)
lockService.addCharacteristic(unlockCharacteristic)
lockService.addCharacteristic(lockStateCharacteristic)
加密开锁流程(手机端):
class SmartLockController(private val bleClient: BleClient) {
private val LOCK_SERVICE_UUID = UUID.fromString("0000FFF0-0000-1000-8000-00805F9B34FB")
private val UNLOCK_CHAR_UUID = UUID.fromString("0000FFF1-0000-1000-8000-00805F9B34FB")
// AES加密工具
private val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding")
private val secretKey = SecretKeySpec("1234567890abcdef".toByteArray(), "AES")
/**
* 发送开锁指令
*/
fun unlock(userId: String) {
// 构造开锁指令
val command = JSONObject().apply {
put("cmd", "unlock")
put("user_id", userId)
put("timestamp", System.currentTimeMillis())
}.toString()
// AES加密
cipher.init(Cipher.ENCRYPT_MODE, secretKey)
val encryptedData = cipher.doFinal(command.toByteArray())
// 通过BLE发送
bleClient.writeCharacteristic(LOCK_SERVICE_UUID, UNLOCK_CHAR_UUID, encryptedData)
}
}
实施效果:
| 指标 | 数据 | 说明 |
|---|---|---|
| 开锁响应时间 | <500ms | 从触发到门锁打开 |
| 连接成功率 | 98.5% | 3米内稳定连接 |
| 电池续航 | 12个月 | CR123A电池(1500mAh) |
| 安全性 | AES-128加密 | 符合金融级加密标准 |
案例2:医疗级血糖仪数据同步
项目背景:
某医疗器械公司的血糖仪需将测量数据同步到手机App,供医生远程查看。
技术方案:
血糖仪BLE数据同步架构
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
血糖仪(GATT Server)
│
├─ 血糖服务(Glucose Service, 0x1808)
│ ├─ 血糖测量特征值(0x2A18)
│ │ - Properties: Notify
│ │ - Value: {glucose: 5.6 mmol/L, time: "2024-02-24 08:00"}
│ │
│ └─ 血糖上下文特征值(0x2A34)
│ - Properties: Notify
│ - Value: {meal: "breakfast", medication: "insulin"}
│
└─ 电池服务(0x180F)
└─ 电池电量(0x2A19)
↓ BLE Notify(自动推送)
手机App(GATT Client)
│
├─ 接收血糖数据
│ └─ 本地存储 + 云端同步
│
└─ 生成血糖趋势图
└─ 异常值告警(低血糖<3.9, 高血糖>11.1)
血糖数据格式(符合Bluetooth SIG标准):
data class GlucoseMeasurement(
val sequenceNumber: Int, // 序列号
val baseTime: Date, // 测量时间
val glucoseConcentration: Float, // 血糖浓度(mmol/L)
val type: GlucoseType, // 样本类型(毛细血管/静脉)
val sampleLocation: SampleLocation, // 采样位置(手指/手臂)
val sensorStatus: Int // 传感器状态
)
// 解析血糖数据(符合GATT Glucose Service规范)
fun parseGlucoseMeasurement(data: ByteArray): GlucoseMeasurement {
val buffer = ByteBuffer.wrap(data).order(ByteOrder.LITTLE_ENDIAN)
val flags = buffer.get().toInt()
val sequenceNumber = buffer.short.toInt()
// 解析时间戳(7字节)
val year = buffer.short.toInt()
val month = buffer.get().toInt()
val day = buffer.get().toInt()
val hour = buffer.get().toInt()
val minute = buffer.get().toInt()
val second = buffer.get().toInt()
val baseTime = Calendar.getInstance().apply {
set(year, month - 1, day, hour, minute, second)
}.time
// 解析血糖浓度(SFLOAT格式,2字节)
val glucoseRaw = buffer.short.toInt()
val glucoseConcentration = decodeSFloat(glucoseRaw)
// 解析类型和位置
val typeAndLocation = if ((flags and 0x02) != 0) buffer.get().toInt() else 0
val type = GlucoseType.values()[(typeAndLocation and 0x0F)]
val sampleLocation = SampleLocation.values()[((typeAndLocation shr 4) and 0x0F)]
return GlucoseMeasurement(
sequenceNumber, baseTime, glucoseConcentration, type, sampleLocation, 0
)
}
// SFLOAT解码(IEEE-11073格式)
fun decodeSFloat(value: Int): Float {
val mantissa = value and 0x0FFF
val exponent = (value shr 12) and 0x0F
return mantissa * 10f.pow(exponent - 3)
}
数据同步实现(手机端):
class GlucoseMeterManager(private val bleClient: BleClient) {
private val GLUCOSE_SERVICE_UUID = UUID.fromString("00001808-0000-1000-8000-00805F9B34FB")
private val GLUCOSE_MEASUREMENT_UUID = UUID.fromString("00002A18-0000-1000-8000-00805F9B34FB")
private val glucoseHistory = mutableListOf<GlucoseMeasurement>()
fun startMonitoring() {
// 启用血糖测量通知
bleClient.enableNotification(GLUCOSE_SERVICE_UUID, GLUCOSE_MEASUREMENT_UUID)
// 监听数据
bleClient.onCharacteristicChanged = { uuid, data ->
if (uuid == GLUCOSE_MEASUREMENT_UUID) {
val measurement = parseGlucoseMeasurement(data)
handleGlucoseData(measurement)
}
}
}
private fun handleGlucoseData(measurement: GlucoseMeasurement) {
// 存储到本地数据库
glucoseHistory.add(measurement)
// 异常值告警
when {
measurement.glucoseConcentration < 3.9f -> {
showAlert("低血糖警告", "当前血糖: ${measurement.glucoseConcentration} mmol/L")
}
measurement.glucoseConcentration > 11.1f -> {
showAlert("高血糖警告", "当前血糖: ${measurement.glucoseConcentration} mmol/L")
}
}
// 同步到云端
uploadToCloud(measurement)
}
}
实施效果:
| 指标 | 数据 | 说明 |
|---|---|---|
| 数据传输成功率 | 99.2% | 500次测试 |
| 数据同步延迟 | <2s | 从测量到App显示 |
| 符合医疗标准 | FDA认证 | 满足ISO 15197:2013 |
| 电池续航 | 1000次测量 | 两节AAA电池 |
案例3:运动手环心率监测
项目背景:
某运动品牌的智能手环需实时传输心率数据到手机App。
技术方案:
- 采样频率:1Hz(每秒1次)
- 传输方式:BLE Notify(无需确认,降低延迟)
- 功耗优化:动态调整连接间隔
连接参数优化:
// 运动模式:低延迟
val sportModeParams = ConnectionParams(
intervalMin = 20, // 20ms
intervalMax = 40, // 40ms
latency = 0, // 无延迟
timeout = 2000 // 2s超时
)
// 日常模式:低功耗
val dailyModeParams = ConnectionParams(
intervalMin = 100, // 100ms
intervalMax = 200, // 200ms
latency = 4, // 跳过4个事件
timeout = 5000 // 5s超时
)
// 动态切换
fun adjustConnectionParams(isExercising: Boolean) {
val params = if (isExercising) sportModeParams else dailyModeParams
bluetoothGatt?.requestConnectionPriority(
if (isExercising) BluetoothGatt.CONNECTION_PRIORITY_HIGH
else BluetoothGatt.CONNECTION_PRIORITY_BALANCED
)
}
实施效果:
| 模式 | 连接间隔 | 延迟 | 功耗 | 续航 |
|---|---|---|---|---|
| 运动模式 | 20-40ms | <50ms | 8mA | 24小时 |
| 日常模式 | 100-200ms | <1s | 0.5mA | 7天 |
案例4:TWS耳机低延迟音频传输
项目背景:
某音频厂商的TWS耳机需实现<80ms的低延迟音频传输(游戏模式)。
技术方案:
- 协议:BLE 5.2 LE Audio(LC3编码)
- 连接间隔:7.5ms(最小值)
- 音频帧大小:120字节/帧
- 编码延迟:10ms(LC3)
延迟分解:
TWS耳机音频延迟分解
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
手机端:
├─ 音频采集 10ms
├─ LC3编码 10ms
└─ BLE发送缓冲 7.5ms
BLE传输:
└─ 空中传输时间 7.5ms(1个连接间隔)
耳机端:
├─ BLE接收处理 5ms
├─ LC3解码 10ms
└─ DAC播放缓冲 20ms
总延迟:10 + 10 + 7.5 + 7.5 + 5 + 10 + 20 = 70ms
实施效果:
| 指标 | 数据 | 对比 |
|---|---|---|
| 音频延迟 | 70ms | 传统蓝牙:200-300ms |
| 音质 | LC3 @ 160kbps | 接近SBC @ 328kbps |
| 功耗 | 5mA(播放) | 传统蓝牙:8mA |
| 续航 | 6小时(单次) | 充电盒30小时 |
案例5:iBeacon室内定位系统
项目背景:
某商场部署iBeacon信标,实现顾客室内导航和精准营销。
技术方案:
iBeacon室内定位架构
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
iBeacon信标(固定位置)
│
├─ 信标A(入口)
│ UUID: E2C56DB5-DFFB-48D2-B060-D0F5A71096E0
│ Major: 1, Minor: 1
│ Tx Power: -59 dBm
│
├─ 信标B(服装区)
│ Major: 1, Minor: 2
│
└─ 信标C(电子区)
Major: 1, Minor: 3
↓ BLE广播
顾客手机App
│
├─ 扫描iBeacon信标
│ └─ 根据RSSI计算距离
│
├─ 三边定位算法
│ └─ 确定顾客位置(精度3-5m)
│
└─ 推送附近商品信息
└─ "您身边的XX商品正在打折!"
iBeacon广播包格式:
// iBeacon广播包结构(31字节)
val iBeaconPacket = byteArrayOf(
// 标准前缀
0x02, 0x01, 0x06, // Flags
0x1A, 0x FF, // 厂商数据长度+类型
0x4C, 0x00, // Apple公司ID
0x02, 0x15, // iBeacon标识
// UUID(16字节)
0xE2.toByte(), 0xC5.toByte(), 0x6D.toByte(), 0xB5.toByte(),
0xDF.toByte(), 0xFB.toByte(), 0x48.toByte(), 0xD2.toByte(),
0xB0.toByte(), 0x60.toByte(), 0xD0.toByte(), 0xF5.toByte(),
0xA7.toByte(), 0x10.toByte(), 0x96.toByte(), 0xE0.toByte(),
// Major(2字节)
0x00, 0x01, // Major = 1
// Minor(2字节)
0x00, 0x01, // Minor = 1
// Tx Power(1字节)
0xC5.toByte() // -59 dBm
)
距离计算(基于RSSI):
fun calculateDistance(rssi: Int, txPower: Int): Double {
if (rssi == 0) return -1.0
val ratio = rssi * 1.0 / txPower
return if (ratio < 1.0) {
ratio.pow(10.0)
} else {
(0.89976) * ratio.pow(7.7095) + 0.111
}
}
// 示例
val distance1 = calculateDistance(-65, -59) // ≈ 2.5m
val distance2 = calculateDistance(-75, -59) // ≈ 8.0m
val distance3 = calculateDistance(-85, -59) // ≈ 20.0m
实施效果:
| 指标 | 数据 | 说明 |
|---|---|---|
| 定位精度 | 3-5m | 三个信标三边定位 |
| 响应速度 | <1s | 从进入区域到推送 |
| 信标续航 | 2年 | CR2032电池(225mAh) |
| 营销转化率提升 | 35% | 精准推送效果 |
六、性能优化实战
6.1 连接稳定性优化
问题:连接频繁断开,特别是在信号较弱或干扰较多的环境。
优化策略:
class ConnectionStabilityManager(private val bluetoothGatt: BluetoothGatt) {
/**
* 策略1:自动重连机制
*/
private var reconnectAttempts = 0
private val maxReconnectAttempts = 3
@SuppressLint("MissingPermission")
fun handleDisconnection() {
if (reconnectAttempts < maxReconnectAttempts) {
reconnectAttempts++
Handler(Looper.getMainLooper()).postDelayed({
bluetoothGatt.connect() // 自动重连
}, 2000L * reconnectAttempts) // 指数退避:2s, 4s, 6s
} else {
Log.e("BLE", "重连失败,已达最大尝试次数")
}
}
/**
* 策略2:连接参数优化
*/
@SuppressLint("MissingPermission")
fun optimizeConnectionParams() {
// 高优先级连接(降低丢包率)
bluetoothGatt.requestConnectionPriority(BluetoothGatt.CONNECTION_PRIORITY_HIGH)
// 延迟600ms后再发现服务(等待连接稳定)
Handler(Looper.getMainLooper()).postDelayed({
bluetoothGatt.discoverServices()
}, 600)
}
/**
* 策略3:MTU协商(减少分包)
*/
@SuppressLint("MissingPermission")
fun requestLargerMtu() {
// 请求更大的MTU(默认23字节,最大512字节)
bluetoothGatt.requestMtu(512)
}
}
实施效果:
| 优化前 | 优化后 | 提升 |
|---|---|---|
| 连接成功率:85% | 连接成功率:98% | +15% |
| 平均断线次数:5次/小时 | 平均断线次数:<1次/小时 | -80% |
6.2 数据传输效率优化
问题:传输大量数据时速度慢,耗时长。
优化策略:
/**
* 策略1:MTU优化(减少分包)
*/
fun negotiateMtu(gatt: BluetoothGatt) {
// 协商更大的MTU
gatt.requestMtu(512) // 512字节(BLE 4.2+)
// MTU协商回调
override fun onMtuChanged(gatt: BluetoothGatt, mtu: Int, status: Int) {
if (status == BluetoothGatt.GATT_SUCCESS) {
Log.d("BLE", "MTU已更新: $mtu 字节")
// 有效载荷 = MTU - 3(ATT头部)
val maxPayload = mtu - 3 // 509字节
}
}
}
/**
* 策略2:数据分块传输
*/
class ChunkedDataTransfer(private val bleClient: BleClient) {
private val CHUNK_SIZE = 509 // MTU=512时的有效载荷
fun sendLargeData(data: ByteArray, serviceUuid: UUID, charUuid: UUID) {
val chunks = data.toList().chunked(CHUNK_SIZE)
Log.d("BLE", "分块传输: ${chunks.size} 块")
chunks.forEachIndexed { index, chunk ->
val chunkData = chunk.toByteArray()
bleClient.writeCharacteristic(serviceUuid, charUuid, chunkData)
// 等待写入完成(避免队列溢出)
Thread.sleep(20) // 20ms间隔
Log.d("BLE", "已发送: ${index + 1}/${chunks.size}")
}
}
}
/**
* 策略3:使用Write Without Response(快速写入)
*/
fun fastWrite(characteristic: BluetoothGattCharacteristic, data: ByteArray): Boolean {
characteristic.value = data
characteristic.writeType = BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE
return bluetoothGatt?.writeCharacteristic(characteristic) == true
}
传输速度对比:
| 方案 | MTU | 写入类型 | 速度 | 适用场景 |
|---|---|---|---|---|
| 默认 | 23字节 | Write | 5 KB/s | 小数据 |
| MTU优化 | 512字节 | Write | 40 KB/s | 需可靠性 |
| 极速模式 | 512字节 | No Response | 80 KB/s | 可容忍丢包 |
6.3 功耗优化
问题:外围设备(传感器/手环)电池续航不足。
优化策略:
/**
* 策略1:动态调整连接间隔
*/
class PowerOptimizer {
fun adjustConnectionInterval(isActive: Boolean, gatt: BluetoothGatt) {
val priority = if (isActive) {
BluetoothGatt.CONNECTION_PRIORITY_HIGH // 7.5-30ms(高功耗)
} else {
BluetoothGatt.CONNECTION_PRIORITY_LOW_POWER // 100-125ms(低功耗)
}
gatt.requestConnectionPriority(priority)
}
/**
* 策略2:使用Slave Latency
*/
fun configureSlave Latency(): ConnectionParams {
return ConnectionParams(
intervalMin = 200, // 200ms连接间隔
intervalMax = 400, // 400ms
latency = 4, // 跳过4个事件 → 实际2s唤醒一次
timeout = 10000 // 10s超时
)
// 功耗降低:80%
// 代价:延迟增加到2s
}
/**
* 策略3:广播间隔优化
*/
fun setAdvertisingInterval(): AdvertisingSettings {
return AdvertisingSettings.Builder()
.setAdvertiseMode(AdvertisingSettings.ADVERTISE_MODE_LOW_POWER) // 1s间隔
.setTxPowerLevel(AdvertisingSettings.ADVERTISE_TX_POWER_ULTRA_LOW) // 超低功率
.setConnectable(true)
.build()
// 续航提升:3倍
}
}
功耗对比(典型传感器):
| 模式 | 连接间隔 | 从机延迟 | 平均功耗 | 续航(500mAh电池) |
|---|---|---|---|---|
| 实时模式 | 20ms | 0 | 8mA | 2.6天 |
| 平衡模式 | 100ms | 2 | 2mA | 10.4天 |
| 省电模式 | 400ms | 4 | 0.5mA | 41.7天 |
七、故障排查方案
问题1:扫描不到设备
现象:调用startScan()后,onScanResult回调从未触发。
排查步骤:
// 步骤1:检查权限
fun checkPermissions(): Boolean {
val permissions = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
arrayOf(
Manifest.permission.BLUETOOTH_SCAN,
Manifest.permission.ACCESS_FINE_LOCATION
)
} else {
arrayOf(Manifest.permission.ACCESS_FINE_LOCATION)
}
return permissions.all {
ContextCompat.checkSelfPermission(context, it) == PackageManager.PERMISSION_GRANTED
}
}
// 步骤2:检查蓝牙和位置服务
fun checkPrerequisites(): Boolean {
val bluetoothAdapter = (context.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager).adapter
val locationManager = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
return bluetoothAdapter?.isEnabled == true &&
locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)
}
// 步骤3:检查扫描过滤器
fun debugScanFilters() {
// 移除所有过滤器,尝试扫描所有设备
bleScanner?.startScan(
emptyList(), // 空过滤器列表
ScanSettings.Builder()
.setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
.build(),
scanCallback
)
}
常见原因与解决方案:
| 原因 | 解决方案 |
|---|---|
| 未授予位置权限 | 动态申请ACCESS_FINE_LOCATION |
| GPS未打开 | 提示用户打开位置服务 |
| Android 12+未授予BLUETOOTH_SCAN | 申请新蓝牙权限 |
| 扫描过滤器过严 | 先用空过滤器测试 |
| 设备未广播 | 检查外围设备是否正常工作 |
问题2:连接失败(Error 133)
现象:onConnectionStateChange返回status=133,连接失败。
原因:Error 133是Android BLE栈的通用错误,可能由多种原因引起。
排查与解决:
class ConnectionErrorHandler(private val device: BluetoothDevice) {
@SuppressLint("MissingPermission")
fun handleError133() {
// 解决方案1:清除GATT缓存(反射调用)
fun clearGattCache(gatt: BluetoothGatt): Boolean {
return try {
val refreshMethod = gatt.javaClass.getMethod("refresh")
refreshMethod.invoke(gatt) as Boolean
} catch (e: Exception) {
false
}
}
// 解决方案2:延迟重连
Handler(Looper.getMainLooper()).postDelayed({
device.connectGatt(context, false, gattCallback)
}, 1000)
// 解决方案3:使用autoConnect=true(后台连接)
device.connectGatt(context, true, gattCallback) // 自动连接模式
}
}
常见原因与解决方案:
| 原因 | 解决方案 |
|---|---|
| GATT缓存损坏 | 调用gatt.refresh()清除缓存 |
| 设备已被其他App连接 | 确保设备断开其他连接 |
| 连接请求过快 | 延迟1s后重试 |
| 设备距离过远 | 靠近设备后重试 |
| 蓝牙栈异常 | 重启蓝牙或重启手机 |
问题3:通知未收到
现象:调用enableNotification()后,onCharacteristicChanged回调从未触发。
排查步骤:
@SuppressLint("MissingPermission")
fun debugNotification(serviceUuid: UUID, charUuid: UUID) {
val characteristic = bluetoothGatt?.getService(serviceUuid)
?.getCharacteristic(charUuid)
// 检查1:特征值是否支持Notify
val supportsNotify = (characteristic?.properties?.and(
BluetoothGattCharacteristic.PROPERTY_NOTIFY) != 0)
Log.d("BLE", "支持Notify: $supportsNotify")
// 检查2:CCCD是否存在
val cccdUuid = UUID.fromString("00002902-0000-1000-8000-00805F9B34FB")
val cccd = characteristic?.getDescriptor(cccdUuid)
Log.d("BLE", "CCCD存在: ${cccd != null}")
// 检查3:是否正确写入CCCD
if (cccd != null) {
cccd.value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE
val success = bluetoothGatt?.writeDescriptor(cccd)
Log.d("BLE", "写入CCCD: $success")
}
// 检查4:是否启用了本地通知
val localEnabled = bluetoothGatt?.setCharacteristicNotification(characteristic, true)
Log.d("BLE", "启用本地通知: $localEnabled")
}
常见原因与解决方案:
| 原因 | 解决方案 |
|---|---|
| 特征值不支持Notify | 检查GATT定义,改用Read轮询 |
| 未写入CCCD | 必须写入0x0001启用 |
| 写入顺序错误 | 先setCharacteristicNotification再writeDescriptor |
| 外围设备未发送 | 检查设备端代码 |
问题4:数据丢失或乱序
现象:Notify数据有时丢失,或收到的数据顺序混乱。
原因与解决:
/**
* 解决方案1:添加序列号
*/
data class Packet(
val sequenceNumber: Int, // 序列号
val payload: ByteArray // 实际数据
)
fun sendWithSequence(data: ByteArray) {
val packet = ByteBuffer.allocate(data.size + 2).apply {
putShort(currentSequence++) // 序列号(2字节)
put(data) // 数据
}.array()
bluetoothGatt?.writeCharacteristic(characteristic, packet)
}
/**
* 解决方案2:使用Indicate代替Notify
*/
fun enableIndication(characteristic: BluetoothGattCharacteristic) {
// Indicate需要确认,保证可靠性
val cccd = characteristic.getDescriptor(CCCD_UUID)
cccd.value = BluetoothGattDescriptor.ENABLE_INDICATION_VALUE
bluetoothGatt?.writeDescriptor(cccd)
}
/**
* 解决方案3:客户端重组数据
*/
class DataReassembler {
private val buffer = mutableMapOf<Int, ByteArray>()
fun addChunk(seq: Int, data: ByteArray) {
buffer[seq] = data
// 检查是否连续
if (buffer.keys.sorted() == (0 until buffer.size).toList()) {
val completeData = buffer.values.fold(byteArrayOf()) { acc, chunk ->
acc + chunk
}
onDataComplete(completeData)
buffer.clear()
}
}
}
八、BLE技术对比
8.1 BLE vs WiFi vs Zigbee
| 对比维度 | BLE | WiFi | Zigbee |
|---|---|---|---|
| 功耗 | ⭐⭐⭐⭐⭐ < 15mA |
⭐⭐ 100-300mA |
⭐⭐⭐⭐ 30-50mA |
| 传输速率 | ⭐⭐⭐ 1-2 Mbps |
⭐⭐⭐⭐⭐ 11-600 Mbps |
⭐ 250 Kbps |
| 传输距离 | ⭐⭐⭐ 10-100m |
⭐⭐⭐⭐ 50-300m |
⭐⭐⭐ 10-100m |
| 连接延迟 | ⭐⭐⭐⭐⭐ 6ms |
⭐⭐⭐ 50-100ms |
⭐⭐⭐ 15-30ms |
| 设备成本 | ⭐⭐⭐⭐⭐ $1-5 |
⭐⭐ $5-20 |
⭐⭐⭐ $3-10 |
| 网络拓扑 | 星型(1对多) | 星型 | Mesh(自组网) |
| 典型应用 | 穿戴、医疗、近场交互 | 视频、大文件、智能家居 | 智能家居、工业自动化 |
| 平台支持 | iOS/Android/Win/Mac/Linux | 全平台 | 需网关 |
选型建议:
- 选BLE:低功耗优先、需手机直连、小数据量、成本敏感
- 选WiFi:需高带宽(视频/音频)、已有WiFi网络、实时性要求不高
- 选Zigbee:需Mesh组网、设备数量多(>50)、不需要手机直连
8.2 BLE 4.x vs BLE 5.x
| 特性 | BLE 4.2 | BLE 5.0 | BLE 5.2 |
|---|---|---|---|
| 传输速率 | 1 Mbps | 2 Mbps | 2 Mbps |
| 传输距离 | 100m | 400m(4倍) | 400m |
| 广播容量 | 31字节 | 255字节(8倍) | 255字节 |
| 数据包长度 | 255字节 | 255字节 | 255字节 |
| 方向查找 | 不支持 | 不支持 | 支持AoA/AoD |
| LE Audio | 不支持 | 不支持 | 支持 |
| 室内定位精度 | 5-10m | 5-10m | <1m |
| 发布时间 | 2014 | 2016 | 2020 |
升级收益:
- BLE 5.0:适用于需要更远距离(仓储追踪)或更大广播包(信标数据)的场景
- BLE 5.2:适用于音频设备(TWS耳机)或室内定位(商场导航)
九、总结
3句话记住BLE
- BLE是专为IoT设计的低功耗蓝牙技术,通过GATT协议实现短距离数据传输,广泛应用于穿戴、医疗、智能家居。
- 核心优势是超低功耗(<15mA活跃,<1μA休眠),纽扣电池可运行数月至数年,连接延迟<6ms。
- GATT层次结构(Service → Characteristic → Descriptor)定义了数据组织方式,Notify机制实现低延迟实时推送。
核心要点
技术选型建议
| 场景 | 是否选BLE | 原因 |
|---|---|---|
| 智能手环/手表 | ✅ 推荐 | 低功耗、手机直连、小数据 |
| 智能门锁 | ✅ 推荐 | 安全、低功耗、成本低 |
| 血糖仪/血压计 | ✅ 推荐 | 医疗标准支持、数据同步 |
| 实时视频监控 | ❌ 不推荐 | 带宽不足,选WiFi |
| 大型智能家居(>50设备) | ❌ 不推荐 | Mesh网络弱,选Zigbee/Thread |
| 车载音频 | ⚠️ 谨慎 | 延迟高,选BLE 5.2 LE Audio |
最佳实践总结
-
连接管理:
- 使用
autoConnect=true实现后台自动重连 - 协商更大MTU(512字节)提升传输效率
- 动态调整连接间隔平衡功耗与延迟
- 使用
-
数据传输:
- 小数据用Notify(无需确认,低延迟)
- 关键数据用Indicate(需确认,保证可靠)
- 大数据分块传输,每块<MTU-3字节
-
功耗优化:
- 利用Slave Latency(跳过无数据事件)
- 广播间隔设置为1s或更长
- 非活跃时切换到低功耗连接模式
-
安全性:
- 使用AES-128加密敏感数据(如门锁指令)
- 配对时启用MITM保护
- 定期更新加密密钥
关键注意事项
- Android 12权限变更:必须申请
BLUETOOTH_SCAN和BLUETOOTH_CONNECT - 位置权限:扫描BLE设备需要
ACCESS_FINE_LOCATION(即使不使用GPS) - GATT缓存:连接异常时调用
gatt.refresh()清除缓存 - 线程安全:所有BLE操作都在主线程回调,避免阻塞UI
- 内存泄漏:及时调用
gatt.close()释放资源
扩展阅读
更多推荐

所有评论(0)