经典蓝牙-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
常用命令
| 命令 | 格式 | 说明 |
|---|---|---|
| RECORD | RECORD|1|timestamp\n | 开始录制(1=开始,0=停止) |
| PAUSE | PAUSE|1|timestamp\n | 暂停录制(1=暂停,0=恢复) |
| PING | PING|timestamp\n | 心跳 |
| ACK | ACK|COMMAND|STATUS|timestamp\n | 确认响应 |
| DATA | DATA|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.ktapp/src/main/java/android/znhaas/bluetooth/BluetoothSppHandler.ktapp/src/main/java/android/znhaas/bluetooth/BluetoothSppConnection.kt
-
协议文档:
- ASCII_BLUETOOTH_PROTOCOL_SPECIFICATION.md
-
架构文档:
- BLUETOOTH_UNIFIED_ARCHITECTURE.md