Android经典蓝牙SPP通信开发与调试实战指南 简介这是一款基于Android Studio开发的蓝牙串口通信调试助手源码项目面向Android应用开发者、嵌入式通信初学者及物联网设备联调人员用于快速实现手机端与蓝牙串口模块如HC-05/HC-06的数据收发、连接管理与状态监控。资源包共946个文件涵盖165个Java类、54个XML布局与配置文件、273个JSON配置及协议定义、232个rawproto序列化描述以及APK构建产物如resources-debug.ap_、DEX字节码和Gradle构建脚本等完整呈现从UI设计、蓝牙权限适配、设备发现含onActivityResult设备地址提取逻辑、Socket连接到数据解析的全链路实现。压缩包大小为14.12MB结构规范便于二次开发与协议扩展。已有1524人学习下载读者可直接导入Android Studio编译运行获取可调试的完整工程、清晰的蓝牙连接状态回调处理范式、典型异常捕获逻辑及适配Android 6.0动态权限的实践方案。1. 这不是普通串口工具——它是一套可深度定制的蓝牙通信底层链路验证框架很多开发者在调试 HC-05、HC-06 或 JDY 系列蓝牙模块时卡在“连上了但收不到数据”“发了指令没响应”“断连后重连失败”这类问题上。市面上的串口助手 App如 SSCom、XCOM、工具侠能快速收发 ASCII/HEX但无法查看 BluetoothSocket 的连接状态机、无法干预 UUID 绑定逻辑、无法复现onActivityResult中设备地址传递异常、更无法修改BluetoothAdapter.getDefaultAdapter()的初始化策略。而这份「蓝牙串口助手(Android Studio源码)」恰恰填补了这个断层它不是一个黑盒应用而是一个完整可编译、可断点、可注入日志、可替换协议栈的 Android 蓝牙通信最小可行系统MVP。它基于原生 Bluetooth API非 BLE覆盖经典蓝牙 SPP 协议栈全流程——从startActivityForResult扫描设备、createRfcommSocketToServiceRecord建立 RFCOMM 通道、到InputStream.read()阻塞读取与OutputStream.write()异步写入。适合嵌入式联调工程师、Android 底层通信开发者、以及需要将蓝牙串口能力集成进自有 App 的中高级 Android 工程师。你拿到的不是 APK而是可直接导入 Android Studio 的工程结构含build.gradle、AndroidManifest.xml权限声明、res/layout界面定义及完整的BluetoothChatService核心服务类。2. 从权限声明到设备发现SPP 协议下经典蓝牙通信的初始化闭环2.1 清单文件中的关键权限与硬件声明必须显式配置Android 12API 31起蓝牙扫描需额外申请BLUETOOTH_SCAN运行时权限且AndroidManifest.xml中必须声明uses-permission与uses-feature。该源码中AndroidManifest.xml的典型配置如下uses-permission android:nameandroid.permission.BLUETOOTH / uses-permission android:nameandroid.permission.BLUETOOTH_ADMIN / uses-permission android:nameandroid.permission.ACCESS_FINE_LOCATION / uses-permission android:nameandroid.permission.BLUETOOTH_SCAN / uses-permission android:nameandroid.permission.BLUETOOTH_CONNECT / uses-feature android:nameandroid.hardware.bluetooth android:requiredtrue /注意ACCESS_FINE_LOCATION是 Android 6.0 扫描必需权限即使不使用 GPSBLUETOOTH_SCAN和BLUETOOTH_CONNECT在 targetSdkVersion ≥ 31 时不可省略否则startDiscovery()直接抛 SecurityException。若目标设备为 Android 10 及以下可移除后两项但需保留BLUETOOTH_ADMIN。2.2 DeviceListActivity基于 startActivityForResult 的设备选择机制解析源码中DeviceListActivity是设备发现核心界面其启动方式为Intent serverIntent new Intent(this, DeviceListActivity.class); startActivityForResult(serverIntent, REQUEST_CONNECT_DEVICE);该 Activity 内部通过BluetoothAdapter.startDiscovery()触发扫描并注册BroadcastReceiver监听BluetoothDevice.ACTION_FOUND与BluetoothAdapter.ACTION_DISCOVERY_FINISHED。关键点在于结果回传逻辑// DeviceListActivity.java 中点击设备项时触发 Intent intent new Intent(); intent.putExtra(DeviceListActivity.DEVICE_ADDRESS, device.getAddress()); setResult(Activity.RESULT_OK, intent); finish();此处DEVICE_ADDRESS是自定义常量字符串如device_address用于在onActivityResult中提取 MAC 地址。常见坑点若DeviceListActivity未正确调用setResult()或putExtra键名拼写错误如大小写不一致则data.getExtras().getString(...)返回 null导致mBluetoothAdapter.getRemoteDevice(address)抛IllegalArgumentException。2.3 onActivityResult 中的地址解析与远程设备获取回到主 Activity 的onActivityResult回调如题干所给代码段其执行流程需严格遵循三步Override public void onActivityResult(int requestCode, int resultCode, Intent data) { Log.e(TAG, onActivityResult); switch (requestCode) { case REQUEST_CONNECT_DEVICE: if (resultCode Activity.RESULT_OK) { // Step 1: 安全提取地址避免空指针 String address data ! null ? data.getStringExtra(DeviceListActivity.DEVICE_ADDRESS) : null; if (address null || !BluetoothAdapter.checkBluetoothAddress(address)) { Log.e(TAG, Invalid Bluetooth address: address); return; } // Step 2: 获取远程设备对象仅地址有效不触发连接 BluetoothDevice device mBluetoothAdapter.getRemoteDevice(address); // Step 3: 启动连接线程通常在此处 new ConnectThread(device).start() connectToDevice(device); } break; } }BluetoothAdapter.checkBluetoothAddress(address)是 Android 提供的校验方法确保 MAC 地址格式为XX:XX:XX:XX:XX:XXgetRemoteDevice(address)仅生成BluetoothDevice实例不建立物理连接真正连接需调用device.createRfcommSocketToServiceRecord(UUID)并执行socket.connect()若跳过地址校验直接传入非法字符串getRemoteDevice会抛出IllegalArgumentException导致 Activity 崩溃。2.4 UUID 绑定SPP 协议的硬性要求与兼容性处理SPPSerial Port Profile服务固定使用标准 UUID00001101-0000-1000-8000-00805F9B34FB。源码中创建 socket 的典型写法为private static final UUID MY_UUID UUID.fromString(00001101-0000-1000-8000-00805F9B34FB); ... BluetoothSocket socket device.createRfcommSocketToServiceRecord(MY_UUID);提示部分国产模块如杰理 AC692x可能使用自定义 UUID此时需与模块厂商确认并替换MY_UUID。若 UUID 不匹配connect()将超时失败IOException: read failed, socket might closed而非立即报错。3. 连接管理与数据收发BluetoothChatService 的线程模型与缓冲区控制3.1 ConnectThread阻塞式连接与异常恢复策略ConnectThread继承Thread核心逻辑封装在run()方法中public void run() { mBluetoothAdapter.cancelDiscovery(); // 取消扫描释放资源 try { mmSocket.connect(); // 阻塞调用超时默认 12 秒 synchronized (BluetoothChatService.this) { mConnectThread null; } connected(mmSocket, mmDevice); // 连接成功回调 } catch (IOException e) { connectionFailed(); // 失败处理关闭 socket通知 UI try { mmSocket.close(); } catch (IOException e2) { Log.e(TAG, unable to close() socket during connection failure, e2); } } }cancelDiscovery()必须在connect()前调用否则在扫描状态下建连成功率极低connect()是同步阻塞操作无内置超时参数实际超时由底层协议栈决定通常 10–15 秒因此需在 UI 层提供“连接中…”提示并禁用按钮异常捕获后必须显式close()否则 socket 句柄泄漏后续连接可能因资源耗尽失败。3.2 ConnectedThread双缓冲区读写与粘包处理ConnectedThread负责长连接下的数据收发其run()方法采用InputStream阻塞读取 OutputStream写入模式public void run() { byte[] buffer new byte[1024]; int bytes; while (true) { try { bytes mmInStream.read(buffer); // 阻塞读取 if (bytes 0) { // 使用 Handler 发送至主线程更新 UI mHandler.obtainMessage(BluetoothChat.MESSAGE_READ, bytes, -1, buffer) .sendToTarget(); } } catch (IOException e) { connectionLost(); // 断连处理 break; } } }mmInStream.read(buffer)返回实际读取字节数非 buffer.length必须用bytes截取有效数据源码中未实现粘包处理即多条指令合并为一次read若设备端以\n或\r\n分隔指令需在MESSAGE_READ处理逻辑中按分隔符切分buffer[0..bytes]mHandler通常绑定主线程 Looper避免在子线程直接操作 View。3.3 数据发送write() 方法的线程安全与缓冲区刷新发送数据通过write(byte[])方法实现public void write(byte[] out) { if (D) Log.d(TAG, write: out.length); OutputStream mmOutStream this.mmOutStream; if (mmOutStream null) return; try { mmOutStream.write(out); mmOutStream.flush(); // 关键强制刷新输出缓冲区 } catch (IOException e) { Log.e(TAG, Exception during write, e); } }flush()调用至关重要Android 的OutputStream默认启用缓冲若不显式flush()数据可能滞留在内核缓冲区导致设备端长时间收不到指令若需发送 HEX 字符串如AA5500FF需先转换为字节数组hexStringToBytes(AA5500FF)避免直接getBytes()产生 ASCII 编码乱码。3.4 连接状态机与生命周期管理表状态触发条件主要动作UI 响应建议STATE_NONE初始状态无显示“未连接”启用“扫描设备”按钮STATE_CONNECTINGconnect()调用后启动ConnectThread显示“正在连接…”禁用所有操作按钮STATE_CONNECTEDconnected()被调用启动ConnectedThread注册Handler显示“已连接”启用发送框与清屏按钮STATE_LISTEN作为服务端监听时启动AcceptThread适用于 PC 端模拟蓝牙串口服务器场景提示源码中BluetoothChatService通常只实现客户端角色主动连接若需服务端监听如手机作为蓝牙串口服务器被 PC 连接需补充AcceptThread并在onStartCommand中启动。4. 调试实战解决 hc05 模块连接不上与数据乱码的四大高频问题4.1 HC-05 连接失败配对、波特率与 AT 指令预置检查清单HC-05 模块在出厂时处于从机模式需确保以下三点物理配对完成手机蓝牙设置中已与 HC-05 配对PIN 码通常为1234或0000且状态为“已配对”而非“可用设备”模块工作模式正确HC-05 默认为“从机”若需手机主动连接无需更改若模块设为“主机”则无法被手机发现AT 指令预置验证使用 USB-TTL 模块通过串口助手发送AT确认返回OK再发ATUART?查看当前波特率常见为9600App 中发送数据的波特率必须与模块一致否则表现为“能连上但收不到数据”。注意Android 端无波特率设置接口波特率由蓝牙模块自身决定App 仅负责数据帧收发。若模块波特率被误设为38400而 App 仍按9600发送数据必然乱码。4.2 数据乱码根因分析与十六进制收发验证法乱码常见于以下场景设备端发送的是二进制数据如传感器原始值0xAA, 0x55, 0x01但 App 以 UTF-8 解析为字符串U\u0001模块返回数据含不可见控制字符如\0,\r,\bTextView 自动过滤显示为空白。验证步骤修改MESSAGE_READ处理逻辑将buffer[0..bytes]转为 HEX 字符串StringBuilder hex new StringBuilder(); for (int i 0; i bytes; i) { hex.append(String.format(%02X , buffer[i])); } Log.d(TAG, HEX received: hex.toString());对比串口助手如 XCOM的 HEX 显示模式确认是否一致若 HEX 一致但字符串显示异常说明是编码问题应改用new String(buffer, 0, bytes, StandardCharsets.ISO_8859_1)解析二进制流。4.3 连接后立即断开Socket 生命周期与 Activity 重建冲突当用户旋转屏幕或切换后台Activity 可能被销毁重建但ConnectedThread仍在后台运行导致mmOutStream被关闭后write()抛IOException。解决方案是将BluetoothChatService提升为独立 Service非内部类并通过bindService()绑定确保其生命周期独立于 Activity// 在 AndroidManifest.xml 中声明 service android:name.BluetoothChatService android:exportedfalse /并在 Activity 中private BluetoothChatService mService; private ServiceConnection mConnection new ServiceConnection() { public void onServiceConnected(ComponentName className, IBinder service) { BluetoothChatService.LocalBinder binder (BluetoothChatService.LocalBinder) service; mService binder.getService(); mService.setHandler(mHandler); // 注入 Handler } // ... };4.4 日志定位法关键 Logcat 过滤命令与典型输出解读在终端执行以下命令聚焦蓝牙通信链路adb logcat -s BluetoothChat:D BluetoothChatService:D BluetoothAdapter:D典型正常流程日志D/BluetoothChat: onActivityResult D/BluetoothChat: Connecting to device: 00:11:22:33:44:55 D/BluetoothChatService: setState() 2 - 3 D/BluetoothChatService: Connected to: 00:11:22:33:44:55 D/BluetoothChat: HEX received: AA 55 01 00 FF若出现D/BluetoothAdapter: createRfcommSocketToServiceRecord后无后续说明connect()阻塞中若出现E/BluetoothChatService: Connection lost则需检查模块供电、距离、配对状态。5. 进阶技巧向源码注入 CRC 校验与指令超时重发机制5.1 在发送前添加 CRC16-CCITT 校验字节为提升通信鲁棒性可在write(byte[])前计算 CRC 并追加。以 CRC16-CCITT0xFFFF 初始化多项式 0x1021为例public static byte[] addCrc16(byte[] data) { int crc 0xFFFF; for (byte b : data) { crc ^ (b 0xFF) 8; for (int i 0; i 8; i) { if ((crc 0x8000) ! 0) { crc (crc 1) ^ 0x1021; } else { crc 1; } } } crc 0xFFFF; byte[] result new byte[data.length 2]; System.arraycopy(data, 0, result, 0, data.length); result[data.length] (byte) ((crc 8) 0xFF); result[data.length 1] (byte) (crc 0xFF); return result; }调用方式byte[] cmd {0xAA, 0x55, 0x01}; mChatService.write(addCrc16(cmd)); // 发送带校验的指令5.2 实现带超时的指令重发队列针对关键指令如设备复位、参数设置可构建CommandQueue类支持自动重发public class CommandQueue { private final QueueCommand queue new ConcurrentLinkedQueue(); private final Handler handler new Handler(Looper.getMainLooper()); public void sendWithRetry(byte[] cmd, int maxRetries, long timeoutMs) { Command c new Command(cmd, maxRetries, timeoutMs); queue.add(c); handler.postDelayed(c::execute, 0); } private class Command implements Runnable { private final byte[] data; private final int maxRetries; private int retries; private final long timeoutMs; Command(byte[] data, int maxRetries, long timeoutMs) { this.data data; this.maxRetries maxRetries; this.timeoutMs timeoutMs; } Override public void run() { if (retries maxRetries) { Log.e(TAG, Command failed after maxRetries retries); return; } mChatService.write(data); handler.postDelayed(() - { if (!ackReceived()) { // 需实现 ackReceived() 检查应答 retries; handler.post(this); // 重发 } }, timeoutMs); } } }此机制将重发逻辑从业务代码中解耦只需调用sendWithRetry(cmd, 3, 2000)即可实现最多 3 次、每次间隔 2 秒的自动重发显著提升工业现场通信可靠性。5.3 蓝牙测距辅助利用 RSSI 值估算模块距离仅作参考虽然经典蓝牙不支持精确测距但可通过BluetoothDevice.fetchUuidsWithSdp()后读取 RSSI 作粗略判断// 在 DeviceListActivity 的 onReceive 中 BluetoothDevice device intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); short rssi intent.getShortExtra(BluetoothDevice.EXTRA_RSSI, Short.MIN_VALUE); Log.d(TAG, Device: device.getName() , RSSI: rssi dBm);RSSI 值范围通常为-127极弱到0极强-40至-60表示近距离1 米内-80以下建议靠近模块。注意此值受环境干扰大仅作辅助参考不可替代 UWB 或 BLE AoA 方案。实际部署时可将 RSSI 值映射为 TextView 背景色绿色→黄色→红色直观提示连接质量。本文还有配套的精品资源点击获取