1. Android 蓝牙开发概述
在移动应用开发中,蓝牙连接功能是许多硬件交互场景的核心需求。作为一名长期从事Android开发的工程师,我经常需要实现与各种蓝牙设备的通信功能。从蓝牙耳机、智能手环到工业传感器,蓝牙技术因其低功耗、稳定性和广泛兼容性而成为硬件连接的首选方案。
Android平台提供了完整的蓝牙API支持,但实际开发中会遇到不少坑点:权限管理复杂、版本兼容性问题、连接稳定性差、线程管理困难等。这些问题如果不处理好,轻则功能异常,重则导致应用崩溃。经过多个项目的实践积累,我总结出了一套稳定可靠的蓝牙连接方案,并将其封装为可直接复用的工具类。
2. 核心设计思路
2.1 架构设计原则
这个蓝牙工具类的设计遵循了几个关键原则:
- 单一职责:每个类和方法只做一件事,保持代码高内聚低耦合
- 开闭原则:通过接口回调扩展功能,而不是修改原有代码
- 线程安全:使用协程替代传统线程管理,避免并发问题
- 资源管理:确保所有蓝牙资源都能正确释放,防止内存泄漏
2.2 技术选型解析
选择Kotlin协程而非RxJava或AsyncTask的原因:
- 协程是Android官方推荐的异步处理方案
- 语法更简洁,可读性更强
- 结构化并发模型更安全可靠
- 与Jetpack组件集成度更高
使用单例模式而非多实例的原因:
- 蓝牙适配器是系统级单例资源
- 避免多个连接互相干扰
- 统一管理连接状态和回调
- 减少资源占用
3. 实现细节详解
3.1 权限管理
Android蓝牙开发最复杂的部分之一就是权限管理。不同Android版本对蓝牙权限的要求差异很大:
kotlin复制// Android 12+需要特殊处理
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
val permissions = arrayOf(
Manifest.permission.BLUETOOTH_SCAN,
Manifest.permission.BLUETOOTH_CONNECT
)
// 检查并请求权限
} else {
// 旧版本需要位置权限
val permissions = arrayOf(
Manifest.permission.ACCESS_FINE_LOCATION
)
}
重要提示:从Android 10开始,后台访问位置信息需要额外声明
ACCESS_BACKGROUND_LOCATION权限,且需要用户在设置中手动授权。
3.2 设备扫描实现
设备扫描是蓝牙连接的第一步,需要注意几个关键点:
- 扫描过滤:通过
BluetoothAdapter.LeScanCallback过滤目标设备 - 扫描时间:建议设置10-15秒的扫描时长,太短可能找不到设备,太长耗电
- 去重处理:使用
ConcurrentHashMap存储已发现设备,避免重复回调
kotlin复制private val scanRunnable = Runnable {
bluetoothAdapter?.bluetoothLeScanner?.stopScan(leScanCallback)
bluetoothCallback?.onScanStateChanged(false)
}
fun startScan(duration: Long = 15000) {
if (!checkPermissions()) return
scannedDevices.clear()
bluetoothAdapter?.bluetoothLeScanner?.startScan(leScanCallback)
mainHandler.postDelayed(scanRunnable, duration)
bluetoothCallback?.onScanStateChanged(true)
}
3.3 连接管理
蓝牙连接是整个功能的核心,需要处理多种异常情况:
- Socket连接:使用
BluetoothDevice.createRfcommSocketToServiceRecord() - 超时处理:设置合理的连接超时(建议5-8秒)
- 重连机制:指数退避算法实现自动重连
kotlin复制private suspend fun connectDeviceInternal(device: BluetoothDevice): Boolean {
return withContext(Dispatchers.IO) {
try {
bluetoothSocket = device.createRfcommSocketToServiceRecord(MY_UUID)
bluetoothSocket?.connect()
true
} catch (e: IOException) {
false
}
}
}
4. 数据通信实现
4.1 数据收发机制
蓝牙数据传输需要建立稳定的输入输出流:
kotlin复制private fun startDataThread() {
if (isReadRunning) return
coroutineScope.launch {
isReadRunning = true
try {
inputStream = bluetoothSocket?.inputStream
outputStream = bluetoothSocket?.outputStream
val buffer = ByteArray(1024)
while (isReadRunning) {
val bytes = inputStream?.read(buffer) ?: break
if (bytes > 0) {
val data = buffer.copyOf(bytes)
notifyDataReceived(data, bytes)
}
}
} catch (e: Exception) {
notifyError(ErrorCode.READ_DATA_FAILED, e.message)
} finally {
isReadRunning = false
}
}
}
4.2 数据分包处理
蓝牙通信中大数据需要分包发送和接收:
- 发送分包:将大数据拆分为MTU大小的包(通常20字节)
- 接收组装:根据协议头尾标识组装完整数据帧
- 校验机制:添加CRC校验或使用协议自带校验
kotlin复制fun sendData(data: ByteArray): Boolean {
return try {
val chunkSize = 20 // 典型蓝牙MTU
for (i in data.indices step chunkSize) {
val end = minOf(i + chunkSize, data.size)
val chunk = data.copyOfRange(i, end)
outputStream?.write(chunk)
outputStream?.flush()
}
true
} catch (e: Exception) {
false
}
}
5. 性能优化与稳定性
5.1 连接保活机制
保持蓝牙连接稳定的关键技术:
- 心跳包:定期发送小数据包保持连接活跃
- 断线检测:监控输入流异常及时触发重连
- 信号强度:监控RSSI值,过低时预警可能断开
kotlin复制private fun startHeartbeat() {
heartbeatTimer = Timer().apply {
scheduleAtFixedRate(object : TimerTask() {
override fun run() {
if (isConnected()) {
sendData(HEARTBEAT_CMD)
}
}
}, HEARTBEAT_DELAY, HEARTBEAT_INTERVAL)
}
}
5.2 资源释放策略
正确的资源释放顺序:
- 先关闭输入输出流
- 再关闭Socket连接
- 最后停止蓝牙适配器
- 取消所有回调引用
kotlin复制fun release() {
try {
inputStream?.close()
outputStream?.close()
bluetoothSocket?.close()
bluetoothAdapter?.close()
} catch (e: Exception) {
// 记录错误但不影响释放流程
} finally {
bluetoothCallback = null
}
}
6. 实际应用案例
6.1 蓝牙打印机连接
典型打印机通信流程:
- 扫描并选择目标打印机
- 发送打印初始化指令
- 按照协议格式发送打印数据
- 接收打印机状态反馈
kotlin复制val manager = BluetoothManager.getInstance(context)
manager.bluetoothCallback = object : BluetoothCallback {
override fun onDataReceived(data: ByteArray, length: Int) {
// 解析打印机状态
}
}
// 发送打印指令
val printData = buildPrintData(content)
manager.sendData(printData)
6.2 工业传感器数据采集
传感器数据采集特点:
- 需要高频率数据接收(50-100ms/次)
- 数据格式通常为二进制协议
- 需要稳定的长连接
kotlin复制// 设置快速数据接收模式
manager.receiveInterval = 50L
// 数据解析示例
fun parseSensorData(data: ByteArray): SensorValue {
val temperature = data[0].toInt() and 0xFF
val humidity = data[1].toInt() and 0xFF
return SensorValue(temperature, humidity)
}
7. 常见问题排查
7.1 连接失败问题
常见原因及解决方案:
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
| 无法发现设备 | 蓝牙未开启/无权限 | 检查权限和蓝牙状态 |
| 配对失败 | 设备已配对但未连接 | 取消旧配对重新连接 |
| 连接超时 | 设备不在范围内 | 靠近设备并重试 |
| 频繁断开 | 信号干扰 | 检查环境或降低通信频率 |
7.2 数据异常问题
数据通信常见故障:
- 数据截断:检查MTU设置和分包逻辑
- 乱码问题:统一收发端编码格式(通常UTF-8)
- 粘包问题:添加帧头帧尾或使用长度前缀
- 数据丢失:增加确认重传机制
8. 高级功能扩展
8.1 多设备连接管理
实现同时连接多个设备的策略:
- 为每个设备创建独立的Manager实例
- 使用连接池管理多个Socket
- 为每个连接分配唯一ID标识
kotlin复制class BluetoothDevicePool {
private val connections = mutableMapOf<String, BluetoothManager>()
fun addConnection(deviceId: String, manager: BluetoothManager) {
connections[deviceId] = manager
}
fun sendToDevice(deviceId: String, data: ByteArray) {
connections[deviceId]?.sendData(data)
}
}
8.2 低功耗优化
针对电池敏感设备的优化措施:
- 减少扫描频率和时长
- 使用更高效的数据编码方式
- 实现按需连接(仅在需要时建立连接)
- 优化心跳包间隔
kotlin复制// 动态调整心跳间隔
fun adjustHeartbeat(batteryLevel: Int) {
val interval = when {
batteryLevel > 70 -> 5000L
batteryLevel > 30 -> 10000L
else -> 30000L
}
setHeartbeatInterval(interval)
}
9. 测试与验证
9.1 单元测试要点
蓝牙功能测试的关键方面:
- 权限请求测试
- 设备发现测试
- 连接稳定性测试
- 数据传输完整性测试
- 异常情况处理测试
kotlin复制@Test
fun testDeviceConnection() {
val manager = BluetoothManager.getInstance(context)
val testDevice = getTestDevice()
runBlocking {
val result = manager.connect(testDevice)
assertTrue(result)
assertTrue(manager.isConnected())
}
}
9.2 真机测试建议
实际设备测试注意事项:
- 准备多种Android版本测试机
- 测试不同厂商设备的兼容性
- 验证长时间运行的稳定性
- 测试弱网环境下的表现
- 验证后台运行能力
10. 性能监控与统计
10.1 关键指标监控
需要监控的核心性能指标:
- 连接建立时间
- 数据传输成功率
- 平均延迟
- 断连频率
- 重连次数
kotlin复制class BluetoothMetrics {
private val metrics = mutableMapOf<String, Any>()
fun recordConnectionTime(time: Long) {
metrics["connection_time"] = time
}
fun recordDataSuccessRate(rate: Float) {
metrics["success_rate"] = rate
}
}
10.2 异常上报机制
建立完善的错误上报系统:
- 分类记录错误类型
- 收集设备环境信息
- 实现分级报警
- 支持远程日志收集
kotlin复制fun reportError(error: ErrorCode, message: String?) {
val errorInfo = mapOf(
"code" to error.name,
"message" to message,
"time" to System.currentTimeMillis(),
"device" to Build.MODEL,
"os" to Build.VERSION.SDK_INT
)
// 上报到服务器
}
在实际项目中应用这套蓝牙工具类时,有几个特别值得注意的经验点:首先,Android 10及以上版本对后台位置权限的限制非常严格,如果应用需要后台扫描功能,务必在应用描述中明确说明用途,否则很难通过应用商店审核;其次,不同厂商的Android设备对蓝牙协议栈的实现存在差异,特别是某些国产定制ROM,可能需要针对特定设备添加兼容性处理;最后,蓝牙通信的稳定性高度依赖物理环境,在实际部署时要充分考虑现场可能存在的无线信号干扰问题。
