You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android Studio中Java线程控制:蓝牙分块发送异常问题排查

问题分析

你遇到的核心问题有两个:

  1. UI线程阻塞导致发送逻辑异常:在btnCarregarMapa方法中,直接在UI线程调用Thread.sleep(5000),虽然Toast会延迟显示,但UI线程被阻塞期间,蓝牙输出流可能因系统缓冲机制,把多次write的数据一次性批量发送。
  2. 蓝牙发送线程设计缺陷: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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.26 12:35:02