Android平台Bluetooth SPP数据乱序及重复读取问题(疑与缓冲区相关)
Hey there, let's dig into this SPP communication problem you're facing—high-frequency 19-byte packets (500 per second) with a cyclic counter, but getting out-of-order data and duplicate old streams? That's a common pitfall with serial over Bluetooth, but we can fix it step by step.
1. Fix InputStream Reading Logic for Fixed-Length Packets
The most likely culprit is incomplete or incorrect reading of your 19-byte packets. The InputStream.read() method doesn't guarantee it'll return all requested bytes in one call—especially with Bluetooth, which can split packets into smaller chunks. If you're not handling partial reads, you'll end up with mangled data or reusing leftover buffer content (which looks like duplicate old data).
Use a loop to ensure you always read exactly 19 bytes per packet:
private void readSPPPacket(InputStream inputStream) throws IOException { byte[] packetBuffer = new byte[19]; int totalBytesRead = 0; while (totalBytesRead < packetBuffer.length) { int bytesRead = inputStream.read(packetBuffer, totalBytesRead, packetBuffer.length - totalBytesRead); if (bytesRead == -1) { // Handle disconnected stream Log.e("SPP", "Bluetooth stream closed unexpectedly"); return; } totalBytesRead += bytesRead; } // Now process the complete 19-byte packet processValidPacket(packetBuffer); }
2. Handle Packet Framing (Sticking/Splitting)
Bluetooth SPP doesn't enforce packet boundaries—multiple 19-byte packets might be merged into a single read, or one packet might be split across multiple reads. To fix this, use a cumulative buffer to collect incoming data and extract complete packets:
private final ByteArrayOutputStream cumulativeBuffer = new ByteArrayOutputStream(); private void processIncomingData(InputStream inputStream) throws IOException { byte[] tempBuffer = new byte[1024]; // Larger buffer to catch merged packets int bytesRead = inputStream.read(tempBuffer); if (bytesRead > 0) { cumulativeBuffer.write(tempBuffer, 0, bytesRead); // Extract all complete 19-byte packets from the buffer while (cumulativeBuffer.size() >= 19) { byte[] fullBuffer = cumulativeBuffer.toByteArray(); // Take first 19 bytes as a valid packet byte[] validPacket = Arrays.copyOf(fullBuffer, 19); processValidPacket(validPacket); // Remove processed bytes from cumulative buffer cumulativeBuffer.reset(); cumulativeBuffer.write(fullBuffer, 19, fullBuffer.length - 19); } } } private void processValidPacket(byte[] packet) { int counter = packet[1] & 0xFF; // Convert to unsigned byte for counter check Log.d("SPP_DEBUG", "Received packet counter: " + counter); // Add your business logic here }
3. Avoid Accidental Mark/Reset Usage
If you're using mark() and reset() on the InputStream, stop immediately. Most Bluetooth SPP input streams don't support reliable marking, and using these methods can cause the stream to re-read old buffered data—exactly the duplicate stream issue you're seeing.
4. Ensure Thread Safety & Consistent Reading Thread
Make sure you're reading from the SPP stream in a single, dedicated background thread. If you're starting/stopping the reading thread frequently, or sharing the stream between multiple threads, you might get race conditions that lead to duplicate or out-of-order data.
Use a thread-safe queue (like LinkedBlockingQueue) to pass complete packets from the reading thread to your processing logic:
private final LinkedBlockingQueue<byte[]> packetQueue = new LinkedBlockingQueue<>(); private boolean isConnected = true; // Reading thread new Thread(() -> { try { while (isConnected) { byte[] packetBuffer = new byte[19]; int totalBytesRead = 0; // Read full packet (reuse the loop from step 1) while (totalBytesRead < packetBuffer.length) { int bytesRead = inputStream.read(packetBuffer, totalBytesRead, packetBuffer.length - totalBytesRead); if (bytesRead == -1) throw new IOException("Stream closed"); totalBytesRead += bytesRead; } packetQueue.put(packetBuffer); } } catch (IOException | InterruptedException e) { Log.e("SPP", "Reading thread error", e); } }).start(); // Processing thread new Thread(() -> { try { while (isConnected) { byte[] packet = packetQueue.take(); processValidPacket(packet); } } catch (InterruptedException e) { Log.e("SPP", "Processing thread interrupted", e); } }).start();
5. Verify Device-Side Packet Transmission
Before blaming your Android code, rule out issues with the Bluetooth device itself:
- Use a serial terminal (like PuTTY or a USB-to-Bluetooth adapter) to capture the device's output directly. Check if the counter bytes are sequential and packets are consistently 19 bytes.
- Ensure the device isn't retransmitting packets unnecessarily (some hardware might retry on perceived lost packets, leading to duplicates).
Quick Debug Tip
Add logging for the counter byte in every processed packet. If you see non-sequential counters or repeated values, you'll know if the issue is happening during transmission or in your app's processing:
private int expectedCounter = 0; private void processValidPacket(byte[] packet) { int currentCounter = packet[1] & 0xFF; Log.d("SPP_COUNTER", "Current: " + currentCounter + " | Expected: " + expectedCounter); expectedCounter = (expectedCounter + 1) % 256; // Wrap around for byte-sized counter }
内容的提问来源于stack exchange,提问作者Sunkyue

