跳到主要内容

经典蓝牙-SPP串口通讯模块

📋 模块概述

SPP(Serial Port Profile)串口通讯模块提供经典蓝牙设备的串口通信功能,支持点对点数据传输、ASCII协议通信、录制控制等功能。

核心类: android.znhaas.bluetooth.BluetoothSppManager

协议处理: android.znhaas.bluetooth.BluetoothSppHandler

连接管理: android.znhaas.bluetooth.BluetoothSppConnection

🎯 主要功能

  • ✅ SPP设备连接/断开
  • ✅ ASCII协议消息收发
  • ✅ 录制控制(开始/停止/暂停/恢复)
  • ✅ 心跳保活机制
  • ✅ Handler线程安全处理
  • ✅ 连接状态监听
  • ✅ 自动重连(可配置)

🏗️ 架构设计

统一蓝牙架构

继承自BaseBluetoothManager,遵循5层架构:

┌─────────────────────────────┐
│ BluetoothSppManager │ 业务管理层
├─────────────────────────────┤
│ BluetoothSppHandler │ 协议处理层
├─────────────────────────────┤
│ BluetoothSppConnection │ 连接管理层
├─────────────────────────────┤
│ BluetoothSocket/RFCOMM │ 传输层
└─────────────────────────────┘

Handler线程模型

使用HandlerThread确保线程安全:

class BluetoothSppManager(context: Context) : BaseBluetoothManager<BluetoothStatusListener>(
context, Protocol.SPP
) {
private val sppHandler = BluetoothSppHandler(context)
private val sppConnection = BluetoothSppConnection(context)

// 协议消息通过Handler异步处理
override fun handleProtocolMessage(msg: Message): Boolean {
return when (msg.what) {
MSG_PROTOCOL_MESSAGE -> {
val protocolMsg = msg.obj as ProtocolMessage
sppHandler.handleRawMessage(...)
true
}
MSG_CONNECTION_STATE -> {
handleConnectionStateChangeInHandler(...)
true
}
else -> false
}
}
}

📚 核心API

1. 初始化

val sppManager = BluetoothSppManager(context)
sppManager.initialize()

2. 连接SPP设备

connectSppDevice(device: BluetoothDeviceInfo): Boolean

val device = BluetoothDeviceInfo(
address = "00:11:22:33:44:55",
name = "znhaas_device",
deviceType = DeviceType.SPP_DEVICE
)

val success = sppManager.connectSppDevice(device)
if (success) {
Log.d(TAG, "SPP设备连接成功")
}

3. 发送消息

sendSppMessage(deviceAddress: String, message: String): Boolean

发送原始字符串消息:

val success = sppManager.sendSppMessage(
deviceAddress = "00:11:22:33:44:55",
message = "HELLO|DATA|${System.currentTimeMillis()}\n"
)

4. 录制控制

开始录制

sppManager.startRecording("00:11:22:33:44:55")

停止录制

sppManager.stopRecording("00:11:22:33:44:55")

暂停录制

sppManager.pauseRecording("00:11:22:33:44:55")

恢复录制

sppManager.resumeRecording("00:11:22:33:44:55")

5. 消息接收

设置消息监听器

sppManager.setMessageListener { address, data ->
val message = String(data, Charsets.UTF_8)
Log.d(TAG, "收到SPP消息: $message from $address")

// 解析ASCII协议消息
val parts = message.trim().split("|")
if (parts.isNotEmpty()) {
when (parts[0]) {
"ACK" -> handleAck(parts)
"DATA" -> handleData(parts)
"STATUS" -> handleStatus(parts)
else -> Log.w(TAG, "未知消息类型: ${parts[0]}")
}
}
}

6. 连接状态监听

sppManager.setConnectionListener { address, isConnected ->
if (isConnected) {
Log.d(TAG, "设备已连接: $address")
statusText.text = "✅ 已连接"
} else {
Log.d(TAG, "设备已断开: $address")
statusText.text = "❌ 未连接"
}
}

7. 断开连接

sppManager.disconnectSppDevice("00:11:22:33:44:55")

8. 清理资源

override fun onDestroy() {
super.onDestroy()
sppManager.cleanup()
}

🔧 ASCII协议格式

SPP通讯使用ASCII文本协议,格式如下:

COMMAND|PARAM1|PARAM2|...|TIMESTAMP\n

常用命令

命令格式说明
RECORDRECORD|1|timestamp\n开始录制(1=开始,0=停止)
PAUSEPAUSE|1|timestamp\n暂停录制(1=暂停,0=恢复)
PINGPING|timestamp\n心跳
ACKACK|COMMAND|STATUS|timestamp\n确认响应
DATADATA|content|timestamp\n数据传输

示例

// 发送开始录制命令
val recordMsg = "RECORD|1|${System.currentTimeMillis()}\n"
sppManager.sendSppMessage(deviceAddress, recordMsg)

// 发送心跳
val pingMsg = "PING|${System.currentTimeMillis()}\n"
sppManager.sendSppMessage(deviceAddress, pingMsg)

// 发送自定义数据
val dataMsg = "DATA|Hello World|${System.currentTimeMillis()}\n"
sppManager.sendSppMessage(deviceAddress, dataMsg)

💡 完整使用示例

class BluetoothSppActivity : AppCompatActivity() {

private lateinit var sppManager: BluetoothSppManager
private var deviceAddress: String? = null

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_bluetooth_spp)

// 初始化
sppManager = BluetoothSppManager(this)
sppManager.initialize()

// 设置监听器
setupListeners()

// 连接按钮
connectButton.setOnClickListener {
connectDevice()
}

// 发送按钮
sendButton.setOnClickListener {
sendMessage()
}

// 开始录制按钮
startRecordButton.setOnClickListener {
deviceAddress?.let { addr ->
sppManager.startRecording(addr)
}
}

// 停止录制按钮
stopRecordButton.setOnClickListener {
deviceAddress?.let { addr ->
sppManager.stopRecording(addr)
}
}
}

private fun setupListeners() {
// 连接状态监听
sppManager.setConnectionListener { address, isConnected ->
runOnUiThread {
if (isConnected) {
deviceAddress = address
statusText.text = "已连接: $address"
statusText.setTextColor(Color.GREEN)
sendButton.isEnabled = true
startRecordButton.isEnabled = true
stopRecordButton.isEnabled = true
} else {
deviceAddress = null
statusText.text = "未连接"
statusText.setTextColor(Color.RED)
sendButton.isEnabled = false
startRecordButton.isEnabled = false
stopRecordButton.isEnabled = false
}
}
}

// 消息接收监听
sppManager.setMessageListener { address, data ->
val message = String(data, Charsets.UTF_8)
runOnUiThread {
receivedText.append("[$address] $message\n")

// 自动滚动到底部
scrollView.post {
scrollView.fullScroll(View.FOCUS_DOWN)
}
}
}
}

private fun connectDevice() {
val device = BluetoothDeviceInfo(
address = "00:11:22:33:44:55",
name = "znhaas_device",
deviceType = DeviceType.SPP_DEVICE
)

lifecycleScope.launch(Dispatchers.IO) {
val success = sppManager.connectSppDevice(device)

withContext(Dispatchers.Main) {
if (success) {
Toast.makeText(this@BluetoothSppActivity, "连接成功", Toast.LENGTH_SHORT).show()
} else {
Toast.makeText(this@BluetoothSppActivity, "连接失败", Toast.LENGTH_SHORT).show()
}
}
}
}

private fun sendMessage() {
val text = inputEditText.text.toString()
if (text.isEmpty()) {
Toast.makeText(this, "请输入消息内容", Toast.LENGTH_SHORT).show()
return
}

deviceAddress?.let { addr ->
val message = "DATA|$text|${System.currentTimeMillis()}\n"
val success = sppManager.sendSppMessage(addr, message)

if (success) {
inputEditText.text.clear()
sentText.append("[发送] $message")
} else {
Toast.makeText(this, "发送失败", Toast.LENGTH_SHORT).show()
}
}
}

override fun onDestroy() {
super.onDestroy()
sppManager.cleanup()
}
}

⚠️ 注意事项

1. 权限要求

<!-- Android 12+ -->
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />

<!-- Android 11及以下 -->
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />

2. UUID

SPP使用标准UUID:

val SPP_UUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB")

3. 线程安全

所有消息处理在Handler线程中执行,回调在主线程:

// ✅ 正确: 回调已在主线程
sppManager.setConnectionListener { address, isConnected ->
// 可以直接更新UI
textView.text = if (isConnected) "已连接" else "未连接"
}

// ✅ 正确: 消息处理可能在子线程
sppManager.setMessageListener { address, data ->
// 如果需要更新UI,切换到主线程
runOnUiThread {
textView.text = String(data)
}
}

4. 消息格式

  • 必须以\n结尾
  • 字段使用|分隔
  • 时间戳使用Unix毫秒时间戳
  • 字符编码为UTF-8

5. 连接管理

  • 连接前确保设备已配对
  • 使用ConcurrentHashMap管理多设备连接
  • 连接断开会自动清理资源

🔗 相关资源

  • 源码位置:

    • app/src/main/java/android/znhaas/bluetooth/BluetoothSppManager.kt
    • app/src/main/java/android/znhaas/bluetooth/BluetoothSppHandler.kt
    • app/src/main/java/android/znhaas/bluetooth/BluetoothSppConnection.kt
  • 协议文档:

    • ASCII_BLUETOOTH_PROTOCOL_SPECIFICATION.md
  • 架构文档:

    • BLUETOOTH_UNIFIED_ARCHITECTURE.md

📚 扩展阅读