Android Studio中Java线程控制:蓝牙分块发送异常问题排查
问题分析
你遇到的核心问题有两个:
- UI线程阻塞导致发送逻辑异常:在
btnCarregarMapa方法中,直接在UI线程调用Thread.sleep(5000),虽然Toast会延迟显示,但UI线程被阻塞期间,蓝牙输出流可能因系统缓冲机制,把多次write的数据一次性批量发送。 - 蓝牙发送线程设计缺陷:
ConnectedThread的enviarBt是静态方法,直接操作静态的mmOutStream,既无线程安全保障,也未对发送操作做顺序控制,同时未调用flush()强制输出流立即发送缓冲数据。
解决方案
1. 重构ConnectedThread,添加消息队列保证顺序发送
将静态成员改为实例成员,添加线程安全队列存储待发送数据,让ConnectedThread在自身线程处理发送逻辑,确保顺序:
public class ConnectedThread extends Thread { private final InputStream mmInStream; private final OutputStream mmOutStream; private final Handler handler; private final BlockingQueue<String> sendQueue = new LinkedBlockingQueue<>(); private volatile boolean isRunning = true; public static final int LEITURA_BT = 1; public ConnectedThread(BluetoothSocket socket, Handler handler) { this.handler = handler; InputStream tmpIn = null; OutputStream tmpOut = null; try { tmpIn = socket.getInputStream(); tmpOut = socket.getOutputStream(); } catch (IOException e) { Log.e(TAG, "Error creating streams", e); } mmInStream = tmpIn; mmOutStream = tmpOut; } @Override public void run() { byte[] buffer = new byte[2048]; int bytes; // 启动独立的发送逻辑线程 new Thread(() -> { while (isRunning) { try { String data = sendQueue.take(); // 阻塞等待队列数据 if (data != null) { byte[] msgBuffer = data.getBytes(); mmOutStream.write(msgBuffer); mmOutStream.flush(); // 强制发送缓冲数据,避免批量输出 } } catch (InterruptedException | IOException e) { Log.e(TAG, "Send failed", e); break; } } }).start(); // 原读取逻辑保持不变 while (isRunning) { try { bytes = mmInStream.read(buffer); String dadosBt = new String(buffer, 0, bytes); handler.obtainMessage(LEITURA_BT, bytes, -1, dadosBt).sendToTarget(); } catch (IOException e) { Log.d(TAG, "Input stream disconnected", e); break; } } } // 改为实例方法,将数据加入发送队列 public void enviarBt(String enviaDados) { try { sendQueue.put(enviaDados); } catch (InterruptedException e) { Log.e(TAG, "Failed to add data to send queue", e); } } // 添加停止方法,避免内存泄漏 public void stopThread() { isRunning = false; interrupt(); try { if (mmInStream != null) mmInStream.close(); if (mmOutStream != null) mmOutStream.close(); } catch (IOException e) { Log.e(TAG, "Error closing streams", e); } } }
2. 修改Activity中的发送逻辑,避免阻塞UI线程
将数据准备和延迟逻辑移到后台线程,不再阻塞UI:
public void btnCarregarMapa(View view) { if (!conexaoBt.retornaConexao()) { return; } // 后台线程处理循环,不影响UI响应 new Thread(() -> { for (int i = 0; i < 3; i++) { // 构建数据块,用StringBuilder提升效率 StringBuilder dataBuilder = new StringBuilder("{\"function\":\"2\""); for (int ii = 0; ii < 23; ii++) { String text = textView[i][ii].getText().toString(); text = String.valueOf(Double.parseDouble(text.replace(",", "."))); dataBuilder.append(",\"p[").append(i).append("][").append(ii).append("\"]:\"").append(text).append("\""); } dataBuilder.append("}"); final String finalData = dataBuilder.toString(); // 在UI线程显示Toast runOnUiThread(() -> Toast.makeText(this, finalData, Toast.LENGTH_SHORT).show()); // 发送数据到队列 conexaoBt.getConnectedThread().enviarBt(finalData); // 后台线程延迟5秒,不阻塞UI try { Thread.sleep(5000); } catch (InterruptedException e) { Thread.currentThread().interrupt(); break; } } }).start(); }
关键修改说明
- 移除静态成员:将输入输出流改为实例成员,避免多实例冲突,保证线程安全。
- 发送队列控制:用
LinkedBlockingQueue实现线程安全的消息队列,确保数据块按加入顺序发送。 - 强制flush输出流:每次写入后调用
flush(),避免系统缓冲导致数据批量发送。 - 后台线程处理循环:将数据准备和延迟逻辑移到后台,不再阻塞UI线程,同时保证Toast在UI线程正常显示。
内容的提问来源于stack exchange,提问作者Bernard Weger
相关产品推荐
相关产品推荐

