蓝牙Socket传输数据损坏问题排查及代码求助
Hey there! Let's break down exactly why your file transfers are getting corrupted (and ballooning to massive sizes like 526MB from a 5MB photo) and fix the root issues. The core problems are all on the receiving end—your updated sending logic is actually correct now, but the receiver isn't handling bytes properly.
Key Issues in the Original Receiver Code
1. Writing the Entire Buffer Every Time (Even Empty/Old Bytes)
In your ConnectedThread.run() method, you're calling fOut?.write(inBuffer) without specifying how many bytes were actually read. The InputStream.read() method returns the number of bytes it successfully pulled into the buffer—but you're writing the entire 1MB buffer every time, including leftover old data from previous reads. That's why your files are way too big and corrupted: you're padding valid data with garbage bytes.
2. Flaky Header Parsing
- Converting the entire buffer to a
Stringto parse the header means you're including uninitialized/empty bytes from the buffer, which can break yoursubstringBefore/substringAfterlogic. - You're not handling cases where the header arrives in multiple chunks (e.g., if it's longer than your initial 1024-byte buffer).
3. Wrong Progress Tracking
Your bytes variable is counting read bytes, but since you're writing the entire buffer, this count doesn't match the actual data written to the file. So your "download complete" check is totally unreliable.
Fixed Code & Solutions
First: Update the Receiver (ConnectedThread)
This fixes all the core issues—we'll write only actual read bytes, parse headers reliably, and track progress correctly.
class ConnectedThread(val socket: BluetoothSocket) : Thread(){ var fileName:String = "" var fileLength:Long = 0L // Use Long to avoid overflow with large files val inStream:InputStream val outStream:OutputStream val outBuffer:ByteArray var inBuffer:ByteArray = ByteArray(1024) // Small buffer for header parsing first var fOut:FileOutputStream? = null var receivedBytes:Long = 0L // Track actual bytes written to file var active = true val notifyManager = BluetoothService.context?.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager var notifyBuilder:NotificationCompat.Builder? = null var isHeaderParsed = false // Flag to track if we've processed the header init { inStream = socket.inputStream outStream = socket.outputStream outBuffer = ByteArray(1000000) notifyBuilder = NotificationCompat.Builder(BluetoothService.context) notifyBuilder?.setSmallIcon(R.drawable.abc_btn_radio_material) ?.setContentTitle("Downloading") } override fun run() { BluetoothService.log("$this waiting to read . . .") val headerBuilder = StringBuilder() // Build header incrementally while (active){ try { val numRead = inStream.read(inBuffer) if (numRead == -1) break // Stream closed, exit loop if (!isHeaderParsed) { // Append the newly read bytes to our header string headerBuilder.append(String(inBuffer, 0, numRead)) val headerStr = headerBuilder.toString() // Use an end marker for reliable header parsing (add this to BTS_Constants) if (headerStr.contains(BTS_Constants.HEADER_END_MARKER)) { // Parse the header: format is START_MARKER + fileName:fileLength:HEADER_END_MARKER val content = headerStr.substringAfter(BTS_Constants.START_MARKER) .substringBefore(BTS_Constants.HEADER_END_MARKER) val parts = content.split(":") if (parts.size == 2) { this.fileName = parts[0] this.fileLength = parts[1].toLong() // Set up the output file val downloadDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS) val outFile = File(downloadDir, fileName) if (outFile.exists()) { BluetoothService.log("File exists, overwriting...") } fOut = outFile.outputStream() // Switch to large buffer for file content inBuffer = ByteArray(1000000) isHeaderParsed = true // Update notification notifyBuilder!!.setContentText("File: $fileName") .setProgress(fileLength.toInt(), 0, false) .setOngoing(true) .setVibrate(longArrayOf(1000L)) notifyManager.notify(BTS_Constants.NOTIFICATION_ID, notifyBuilder!!.build()) BluetoothService.log("Name: $fileName, Length: $fileLength") } } } else { // Write ONLY the bytes we actually read (not the whole buffer!) fOut?.write(inBuffer, 0, numRead) receivedBytes += numRead // Update progress notification notifyBuilder!!.setProgress(fileLength.toInt(), receivedBytes.toInt(), false) notifyManager.notify(BTS_Constants.NOTIFICATION_ID, notifyBuilder!!.build()) // Check if we've received the full file if (receivedBytes >= fileLength) { notifyBuilder!!.setProgress(0, 0, false) .setContentText("Download Complete") .setOngoing(false) notifyManager.notify(BTS_Constants.NOTIFICATION_ID, notifyBuilder!!.build()) // Clean up resources fOut?.flush() fOut?.close() fOut = null // Optional: stop thread or wait for next file // active = false } } BluetoothService.log("Read $numRead bytes, Total Received: $receivedBytes") }catch (ioe:IOException){ BluetoothService.log("Failed to read from $socket", ioe) cancel() }finally { // Ensure we close the file stream if the thread stops if (!active) { fOut?.close() } } } } // Your updated write method is already correct! fun write(bytes: ByteArray, offset: Int, length: Int){ BluetoothService.log("Writing bytes from $this") outStream.write(bytes, offset, length) outStream.flush() BluetoothService.log("Bytes written") } fun cancel(){ BluetoothService.log("Closing socket - read failure expected") active = false socket.close() } }
Second: Update the Sender's Header
Add an end marker to your BTS_Constants to make header parsing reliable:
const val HEADER_END_MARKER = "||HEADER_END||"
Then modify the header sending code in your sender thread:
val header = "${BTS_Constants.START_MARKER}$fileName:$length:${BTS_Constants.HEADER_END_MARKER}".toByteArray() BluetoothService.send(header, 0, header.size)
Extra Tips to Avoid Future Issues
- Use Long for File Lengths: Int can only handle files up to ~2GB—switch to Long to support larger files.
- Add Integrity Checks: Send an MD5 hash of the file after transfer, then have the receiver compute and compare it to ensure the file is intact.
- Handle Edge Cases: What if the connection drops mid-transfer? Add logic to resume or clean up partial files.
内容的提问来源于stack exchange,提问作者mox_mox

