1. Android BLE开发核心流程解析
在物联网和智能硬件蓬勃发展的当下,BLE(蓝牙低功耗)技术已成为Android设备与周边配件通信的首选方案。与经典蓝牙相比,BLE在功耗和连接效率上具有明显优势,特别适合心率带、智能手环等需要长时间运行的设备。但Android平台的BLE开发却有着独特的挑战——整个通信链路由多个异步回调构成,稍有不慎就会陷入"回调地狱"。
1.1 BLE通信基础架构
BLE通信建立在GATT(通用属性规范)协议之上,其核心是分层服务架构:
- 服务(Service):设备功能的逻辑分组,如电池服务、设备信息服务
- 特征值(Characteristic):服务中的具体数据点,包含读写属性
- 描述符(Descriptor):描述特征值的元数据,如CCCD(客户端特征配置描述符)
典型的通信流程需要依次完成:
- 扫描发现周边BLE设备
- 建立GATT连接
- 发现设备支持的服务和特征值
- 配置通知/指示(用于接收数据)
- 通过特征值读写进行数据交换
1.2 Android BLE开发痛点
Android BLE API设计最显著的特点是全异步回调。这意味着:
- 扫描结果通过
ScanCallback返回 - 连接状态变化通过
BluetoothGattCallback通知 - 每次读写操作都有对应的回调方法
这种设计虽然避免了阻塞主线程,但也带来了代码组织上的挑战。开发者必须严格管理操作顺序,比如必须在服务发现完成后才能进行特征值读写,否则会触发GATT_INVALID_OPERATION错误。
2. 开发环境准备与权限配置
2.1 权限声明策略
从Android 12(API 31)开始,Google对BLE权限模型进行了重大调整。在AndroidManifest.xml中需要声明:
xml复制<!-- Android 12+ 必备权限 -->
<uses-permission android:name="android.permission.BLUETOOTH_SCAN"
android:usesPermissionFlags="neverForLocation"/>
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT"/>
<!-- 兼容旧版本的定位权限 -->
<uses-permission
android:name="android.permission.ACCESS_FINE_LOCATION"
android:maxSdkVersion="30"/>
关键注意事项:
BLUETOOTH_SCAN需要添加neverForLocation标志,除非确实需要获取位置信息- 定位权限在Android 6.0+需要运行时申请,即使targetSdkVersion≥31也要处理
2.2 运行时权限处理
在Activity或Fragment中实现权限申请:
kotlin复制private val PERMISSION_REQUEST_CODE = 100
private val requiredPermissions = arrayOf(
Manifest.permission.BLUETOOTH_SCAN,
Manifest.permission.BLUETOOTH_CONNECT,
Manifest.permission.ACCESS_FINE_LOCATION
)
fun checkPermissions() {
when {
ContextCompat.checkSelfPermission(
this,
Manifest.permission.BLUETOOTH_CONNECT
) == PackageManager.PERMISSION_GRANTED -> {
// 权限已授予
startBleOperations()
}
ActivityCompat.shouldShowRequestPermissionRationale(
this,
Manifest.permission.BLUETOOTH_CONNECT
) -> {
// 解释权限必要性
showPermissionExplanationDialog()
}
else -> {
// 直接申请权限
ActivityCompat.requestPermissions(
this,
requiredPermissions,
PERMISSION_REQUEST_CODE
)
}
}
}
重要提示:在Android 10及以上版本,即使已经授予定位权限,如果关闭了设备的GPS开关,某些厂商的设备仍然会返回空扫描结果。这是厂商定制ROM的行为,需要在代码中做兼容处理。
3. BLE设备扫描实现详解
3.1 扫描参数配置
Android提供多种扫描模式,需要根据场景选择:
kotlin复制val scanSettings = ScanSettings.Builder()
.setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY) // 低延迟模式
.setLegacy(false) // 是否兼容旧设备
.setPhy(ScanSettings.PHY_LE_ALL_SUPPORTED) // 使用设备支持的所有PHY
.build()
// 可选:设置扫描过滤器
val filters = listOf(
ScanFilter.Builder()
.setServiceUuid(ParcelUuid(SERVICE_UUID))
.build()
)
bluetoothLeScanner.startScan(filters, scanSettings, scanCallback)
扫描模式对比:
| 扫描模式 | 功耗 | 扫描间隔 | 适用场景 |
|---|---|---|---|
| SCAN_MODE_LOW_POWER | 低 | 4.5s间隔 | 后台扫描 |
| SCAN_MODE_BALANCED | 中 | 2s间隔 | 一般应用 |
| SCAN_MODE_LOW_LATENCY | 高 | 持续扫描 | 实时性要求高 |
3.2 扫描结果处理优化
在实际项目中,需要对扫描结果进行去重和过滤:
kotlin复制private val foundDevices = mutableMapOf<String, BluetoothDevice>()
private val scanCallback = object : ScanCallback() {
override fun onScanResult(callbackType: Int, result: ScanResult) {
val device = result.device
device?.let {
// 信号强度过滤
if (result.rssi < -80) return
// 设备名称过滤
if (it.name?.contains("MyDevice") == true) {
foundDevices[it.address] = it
updateDeviceList(foundDevices.values.toList())
}
}
}
}
常见问题排查:
-
扫描不到设备:
- 检查是否已授予所有必要权限
- 确认设备未被其他应用连接(BLE设备通常不支持多连接)
- 尝试关闭/重新打开手机蓝牙
-
扫描结果不稳定:
- 避免在
onScanResult中执行耗时操作 - 考虑增加信号强度阈值过滤
- 检查设备是否处于广播状态
- 避免在
4. 设备连接与服务发现
4.1 连接建立过程
建立GATT连接是BLE通信的关键一步:
kotlin复制private var bluetoothGatt: BluetoothGatt? = null
fun connectDevice(device: BluetoothDevice) {
// 先停止扫描
bluetoothLeScanner.stopScan(scanCallback)
// 建立连接(autoConnect参数设为false以获得更快的连接速度)
bluetoothGatt = device.connectGatt(
context,
false,
gattCallback,
BluetoothDevice.TRANSPORT_LE // 明确使用BLE传输
)
}
连接参数说明:
autoConnect:设为true时系统会维护长期连接,但首次连接速度较慢TRANSPORT_LE:确保使用BLE而非经典蓝牙传输PHY_LE_1M/2M/CODED:Android 8.0+可指定物理层参数
4.2 服务发现流程
连接建立后,必须发现服务才能进行后续操作:
kotlin复制private val gattCallback = object : BluetoothGattCallback() {
override fun onConnectionStateChange(
gatt: BluetoothGatt,
status: Int,
newState: Int
) {
when (newState) {
BluetoothProfile.STATE_CONNECTED -> {
// 重要:发现服务前先请求更高的MTU
gatt.requestMtu(247)
gatt.discoverServices()
}
BluetoothProfile.STATE_DISCONNECTED -> {
cleanupConnection()
}
}
}
override fun onMtuChanged(gatt: BluetoothGatt, mtu: Int, status: Int) {
if (status == BluetoothGatt.GATT_SUCCESS) {
Log.d("BLE", "MTU updated to $mtu")
}
}
override fun onServicesDiscovered(gatt: BluetoothGatt, status: Int) {
if (status == BluetoothGatt.GATT_SUCCESS) {
validateServices(gatt)
}
}
}
经验之谈:在
discoverServices()之前先调用requestMtu(247)可以显著提高后续数据传输效率。Android默认使用23字节的MTU,这对于需要传输大量数据的应用远远不够。
5. 数据通信实现
5.1 启用通知配置
接收设备数据通常通过通知(Notification)或指示(Indication)实现:
kotlin复制private fun enableNotifications(gatt: BluetoothGatt) {
val service = gatt.getService(SERVICE_UUID) ?: return
val characteristic = service.getCharacteristic(NOTIFY_UUID) ?: return
// 第一步:设置本地通知
gatt.setCharacteristicNotification(characteristic, true)
// 第二步:配置CCCD描述符
val descriptor = characteristic.getDescriptor(CCCD_UUID).apply {
value = when {
characteristic.properties and BluetoothGattCharacteristic.PROPERTY_INDICATE != 0 -> {
BluetoothGattDescriptor.ENABLE_INDICATION_VALUE
}
else -> {
BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE
}
}
}
gatt.writeDescriptor(descriptor)
}
通知与指示的区别:
- 通知(Notification):设备发送数据后不要求确认,可能丢失
- 指示(Indication):设备发送数据后等待客户端确认,更可靠
5.2 数据写入技巧
向设备发送数据时需要考虑MTU限制:
kotlin复制fun sendData(data: ByteArray) {
val characteristic = getWriteCharacteristic() ?: return
// 分片发送逻辑(当数据超过MTU-3时)
val chunkSize = bluetoothGatt?.mtu?.minus(3) ?: 20
data.toList().chunked(chunkSize).forEach { chunk ->
characteristic.value = chunk.toByteArray()
bluetoothGatt?.writeCharacteristic(
characteristic,
chunk.toByteArray(),
when {
characteristic.properties and BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE != 0 ->
BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE
else ->
BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT
}
)
}
}
写入类型选择:
WRITE_TYPE_DEFAULT:要求设备响应(可靠但速度慢)WRITE_TYPE_NO_RESPONSE:不要求响应(快速但可能丢失)
5.3 数据接收处理
接收到的数据需要根据协议解析:
kotlin复制override fun onCharacteristicChanged(
gatt: BluetoothGatt,
characteristic: BluetoothGattCharacteristic,
value: ByteArray
) {
when (characteristic.uuid) {
NOTIFY_UUID -> handleNotificationData(value)
else -> Log.w("BLE", "Unknown characteristic changed")
}
}
private fun handleNotificationData(data: ByteArray) {
// 示例:解析心率数据
if (data.isNotEmpty()) {
val flags = data[0].toInt()
val is16Bit = flags and 0x01 != 0
val heartRate = if (is16Bit) {
(data[1].toInt() and 0xFF) or
((data[2].toInt() and 0xFF) shl 8)
} else {
data[1].toInt() and 0xFF
}
Log.d("BLE", "Heart rate: $heartRate bpm")
}
}
6. 连接管理与错误处理
6.1 连接状态维护
需要妥善管理连接生命周期:
kotlin复制private var connectionState = STATE_DISCONNECTED
fun disconnect() {
bluetoothGatt?.let { gatt ->
// 先取消通知
getNotifyCharacteristic()?.let { characteristic ->
gatt.setCharacteristicNotification(characteristic, false)
}
// 再断开连接
gatt.disconnect()
}
}
private fun cleanupConnection() {
bluetoothGatt?.close()
bluetoothGatt = null
connectionState = STATE_DISCONNECTED
}
6.2 常见错误处理
在BluetoothGattCallback中处理各种错误:
kotlin复制override fun onDescriptorWrite(
gatt: BluetoothGatt,
descriptor: BluetoothGattDescriptor,
status: Int
) {
when (status) {
BluetoothGatt.GATT_SUCCESS -> {
Log.d("BLE", "Descriptor write success")
}
BluetoothGatt.GATT_INSUFFICIENT_AUTHENTICATION -> {
// 需要绑定设备
createBond(descriptor.characteristic.service.device)
}
else -> {
Log.e("BLE", "Descriptor write failed: $status")
retryDescriptorWrite(descriptor)
}
}
}
private fun createBond(device: BluetoothDevice) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
device.createBond()
}
}
常见GATT错误码:
| 错误码 | 含义 | 解决方案 |
|---|---|---|
| GATT_SUCCESS (0) | 操作成功 | - |
| GATT_READ_NOT_PERMITTED (2) | 不可读 | 检查特征值属性 |
| GATT_INSUFFICIENT_AUTHENTICATION (5) | 认证不足 | 绑定设备 |
| GATT_CONNECTION_CONGESTED (143) | 连接拥塞 | 降低发送频率 |
7. 性能优化与高级技巧
7.1 连接参数优化
Android 8.0+支持调整BLE连接参数:
kotlin复制if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
bluetoothGatt?.requestConnectionPriority(
BluetoothGatt.CONNECTION_PRIORITY_HIGH
)
}
连接参数选项:
CONNECTION_PRIORITY_BALANCED:平衡模式(默认)CONNECTION_PRIORITY_HIGH:高频率(11.25-15ms间隔)CONNECTION_PRIORITY_LOW_POWER:低功耗(100-125ms间隔)
7.2 数据分包与重组
处理超过MTU的大数据包:
kotlin复制private val packetBuffer = mutableListOf<ByteArray>()
private var expectedLength = 0
fun handleIncomingPacket(packet: ByteArray) {
when {
packet.size == 1 && packet[0] == 0x02.toByte() -> {
// 开始包
packetBuffer.clear()
}
packet.size == 1 && packet[0] == 0x03.toByte() -> {
// 结束包
val completeData = packetBuffer.flatMap { it.toList() }.toByteArray()
processCompleteData(completeData)
packetBuffer.clear()
}
else -> {
// 数据包
packetBuffer.add(packet)
}
}
}
7.3 后台连接策略
在Android 8.0+上保持后台连接:
xml复制<service
android:name=".BluetoothLeService"
android:enabled="true"
android:exported="false"
android:foregroundServiceType="connectedDevice"
android:permission="android.permission.FOREGROUND_SERVICE"/>
在Service中:
kotlin复制override fun onCreate() {
super.onCreate()
startForeground(
NOTIFICATION_ID,
createNotification(),
ServiceInfo.FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE
)
}
8. 实战问题排查指南
8.1 连接失败排查步骤
- 检查设备是否在范围内且未被其他设备连接
- 验证蓝牙适配器是否已开启
- 确认已授予所有必要权限
- 尝试重启设备蓝牙
- 检查设备是否要求配对绑定
8.2 数据收发异常处理
发送失败:
- 检查特征值是否具有写属性
- 确认写入类型与特征值属性匹配
- 验证数据长度不超过MTU-3
接收不到通知:
- 确认已正确配置CCCD描述符
- 检查设备是否确实发送了通知
- 验证特征值是否具有通知/指示属性
8.3 厂商特定问题
某些Android设备存在特定问题:
- 华为/荣耀:在省电模式下可能限制BLE后台扫描
- 小米:可能需要手动开启"自启动"权限
- OPPO:锁屏后可能断开BLE连接
解决方案:
- 引导用户将应用加入电池优化白名单
- 使用Foreground Service维持连接
- 实现自动重连机制
9. 架构设计与代码组织
9.1 分层架构建议
code复制├── ble
│ ├── BleManager.kt // 核心BLE操作
│ ├── BleCommand.kt // 命令封装
│ ├── BleResponse.kt // 响应解析
│ └── model
│ ├── BleDevice.kt // 设备模型
│ └── Service.kt // 服务模型
└── ui
├── DeviceListFragment.kt
└── DeviceControlActivity.kt
9.2 使用Kotlin协程封装
将回调转换为挂起函数:
kotlin复制class BleOperationHandler {
private val operationQueue = Channel<BleOperation>(capacity = Channel.UNLIMITED)
private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
init {
scope.launch {
for (operation in operationQueue) {
executeOperation(operation)
}
}
}
suspend fun writeCharacteristic(
characteristic: BluetoothGattCharacteristic,
data: ByteArray
): Result<Unit> = suspendCoroutine { continuation ->
operationQueue.trySend(
WriteOperation(characteristic, data, continuation)
)
}
private suspend fun executeOperation(operation: BleOperation) {
when (operation) {
is WriteOperation -> {
val success = gatt.writeCharacteristic(
operation.characteristic,
operation.data,
WRITE_TYPE_DEFAULT
)
operation.continuation.resume(
if (success) Result.success(Unit)
else Result.failure(IOException("Write failed"))
)
}
// 其他操作类型...
}
}
}
9.3 状态管理最佳实践
使用密封类管理连接状态:
kotlin复制sealed class ConnectionState {
object Disconnected : ConnectionState()
data class Connecting(val device: BluetoothDevice) : ConnectionState()
data class Connected(val device: BluetoothDevice) : ConnectionState()
data class Disconnecting(val device: BluetoothDevice) : ConnectionState()
data class Error(val exception: Exception) : ConnectionState()
}
private val _connectionState = MutableStateFlow<ConnectionState>(ConnectionState.Disconnected)
val connectionState: StateFlow<ConnectionState> = _connectionState.asStateFlow()
10. 测试与调试技巧
10.1 使用nRF Connect调试
nRF Connect是强大的BLE调试工具,可用于:
- 查看设备广播数据
- 测试GATT服务发现
- 手动读写特征值
- 监控连接参数
10.2 Android Studio蓝牙日志
启用详细蓝牙日志:
bash复制adb shell setprop log.tag.Bluetooth VERBOSE
adb shell setprop log.tag.BluetoothGatt VERBOSE
adb logcat -v threadtime -s Bluetooth
10.3 自动化测试策略
使用AndroidX Test框架进行BLE测试:
kotlin复制@RunWith(AndroidJUnit4::class)
class BleManagerTest {
@get:Rule
val bluetoothRule = BluetoothTestRule()
@Test
fun testDeviceConnection() {
val bleManager = BleManager(ApplicationProvider.getApplicationContext())
val testDevice = bluetoothRule.connectTestDevice()
bleManager.connect(testDevice.address)
assertThat(bleManager.connectionState.value)
.isInstanceOf(ConnectionState.Connected::class.java)
}
}
11. 跨版本兼容性处理
11.1 Android版本差异
| 版本 | 重要变更 |
|---|---|
| Android 4.3 (API 18) | 引入BLE支持 |
| Android 5.0 (API 21) | 新增扫描过滤器 |
| Android 6.0 (API 23) | 需要运行时定位权限 |
| Android 8.0 (API 26) | 后台扫描限制 |
| Android 10 (API 29) | 限制后台位置访问 |
| Android 12 (API 31) | 新BLE权限模型 |
11.2 兼容性封装示例
kotlin复制fun checkBluetoothAvailability(context: Context): Boolean {
return when {
!context.packageManager.hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE) -> {
false // 设备不支持BLE
}
(bluetoothAdapter?.isEnabled != true) -> {
false // 蓝牙未开启
}
Build.VERSION.SDK_INT >= Build.VERSION_CODES.S &&
!hasPermissions(
context,
Manifest.permission.BLUETOOTH_SCAN,
Manifest.permission.BLUETOOTH_CONNECT
) -> {
false // 缺少新权限
}
else -> true
}
}
12. 安全最佳实践
12.1 通信安全建议
- 使用绑定/配对建立安全连接
- 对敏感数据实施加密
- 验证设备认证信息
- 实现MITM保护机制
12.2 安全连接示例
kotlin复制private val pairingBroadcastReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
when (intent.action) {
BluetoothDevice.ACTION_BOND_STATE_CHANGED -> {
val device = intent.getParcelableExtra<BluetoothDevice>(
BluetoothDevice.EXTRA_DEVICE
)
when (device?.bondState) {
BluetoothDevice.BOND_BONDED -> {
// 绑定成功,重新连接
connectDevice(device)
}
BluetoothDevice.BOND_NONE -> {
// 绑定失败处理
}
}
}
}
}
}
fun createSecureConnection(device: BluetoothDevice) {
if (device.bondState == BluetoothDevice.BOND_BONDED) {
connectDevice(device)
} else {
// 注册绑定状态接收器
val filter = IntentFilter(BluetoothDevice.ACTION_BOND_STATE_CHANGED)
context.registerReceiver(pairingBroadcastReceiver, filter)
// 发起配对
device.createBond()
}
}
13. 功耗优化技巧
13.1 低功耗策略
- 在后台使用
SCAN_MODE_LOW_POWER - 适当延长扫描间隔
- 及时释放不使用的GATT连接
- 使用
WRITE_TYPE_NO_RESPONSE减少交互
13.2 电量监控实现
kotlin复制private fun monitorBatteryUsage() {
val powerManager = context.getSystemService(Context.POWER_SERVICE)
as PowerManager
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
val batteryStats = powerManager
.createWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "BLE:WakeLock")
.apply {
setReferenceCounted(false)
}
bleManager.onOperationStarted = {
if (!batteryStats.isHeld) {
batteryStats.acquire(10 * 60 * 1000L /*10分钟*/)
}
}
bleManager.onOperationFinished = {
if (batteryStats.isHeld) {
batteryStats.release()
}
}
}
}
14. 扩展功能实现
14.1 设备固件升级(DFU)
实现OTA固件更新:
kotlin复制class DfuManager(
private val bleManager: BleManager,
private val fileUri: Uri
) {
private var firmwareData: ByteArray? = null
private var currentOffset = 0
suspend fun startUpgrade() {
loadFirmwareData()
enterBootloaderMode()
sendFirmwareChunks()
verifyChecksum()
resetDevice()
}
private suspend fun sendFirmwareChunks() {
firmwareData?.let { data ->
val chunkSize = bleManager.mtu - 3
while (currentOffset < data.size) {
val chunk = data.copyOfRange(
currentOffset,
minOf(currentOffset + chunkSize, data.size)
)
bleManager.writeDfuCharacteristic(chunk)
currentOffset += chunk.size
delay(50) // 控制发送速率
}
}
}
}
14.2 多设备连接管理
kotlin复制class MultiBleManager {
private val connectedDevices = mutableMapOf<String, BluetoothGatt>()
fun connectDevice(device: BluetoothDevice) {
if (connectedDevices.containsKey(device.address)) return
val gatt = device.connectGatt(context, false, object : BluetoothGattCallback() {
// 回调实现...
})
connectedDevices[device.address] = gatt
}
fun broadcastMessage(message: ByteArray) {
connectedDevices.values.forEach { gatt ->
getWriteCharacteristic(gatt)?.let { characteristic ->
gatt.writeCharacteristic(
characteristic,
message,
BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE
)
}
}
}
}
15. 性能监控与统计
15.1 关键指标采集
kotlin复制class BleMetrics {
private val connectionTimes = mutableListOf<Long>()
private val rssiValues = mutableListOf<Int>()
private val throughputs = mutableListOf<Double>()
fun logConnectionTime(timeMs: Long) {
connectionTimes.add(timeMs)
}
fun logRssi(rssi: Int) {
rssiValues.add(rssi)
}
fun logThroughput(bytesPerSecond: Double) {
throughputs.add(bytesPerSecond)
}
fun generateReport(): BlePerformanceReport {
return BlePerformanceReport(
avgConnectionTime = connectionTimes.average(),
avgRssi = rssiValues.average(),
avgThroughput = throughputs.average()
)
}
}
15.2 实时监控实现
kotlin复制class BleMonitorService : Service() {
private val binder = LocalBinder()
private val _throughput = MutableLiveData<Double>()
val throughput: LiveData<Double> = _throughput
inner class LocalBinder : Binder() {
fun getService(): BleMonitorService = this@BleMonitorService
}
override fun onBind(intent: Intent): IBinder = binder
fun startMonitoring(gatt: BluetoothGatt) {
// 实现监控逻辑...
}
}
