处理程序消息数据被下一条消息覆盖的Java线程问题

时间:2011-06-20 21:45:26

标签: java android multithreading handler

我有一个线程从蓝牙流中读取数据,该数据将数据发送到主UIThread上的处理程序(基于Bluetooth Chat Sample)。

我发现了一个经常弹出的线程问题。首先,一些代码供参考。

BluetoothService.java (只是读取数据流的部分。在此代码运行之前已正确设置。)

public void run() {
     DebugLog.i("BluetoothService", "BEGIN mConnectedThread");
     byte[] buffer = new byte[1024];
     int bytes;
         // Keep listening to the InputStream while connected
     while (true) {
          try {
             // Read from the InputStream
             bytes = mmInStream.read(buffer);

             DebugLog.d("BluetoothService", new String(buffer, 0, bytes));
             // Send the obtained bytes to the UI Activity
             mHandler.obtainMessage(MESSAGE_READ, bytes, -1, buffer)
                          .sendToTarget();
          } catch (IOException e) {
                DebugLog.e(TAG, "disconnected", e);
                connectionLost();
                break;
          }
     }
}

我的主要活动(部分)中定义的处理程序:

// The Handler that gets information back from the BluetoothService
private final Handler mHandler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
        switch (msg.what) {
          case BluetoothService.MESSAGE_READ:
            byte[] readBuf = (byte[]) msg.obj;
            // construct a string from the valid bytes in the buffer
       String readMessage = new String(readBuf, 0, msg.arg1);
       DebugLog.d("BluetoothHandler", readMessage);
       mConversationArrayAdapter.add(mConnectedDeviceName+":  " + readMessage);
                 break;

        }
    }
};

当一小段连续的数据流进入mmInStream时,我的问题就来了。例如,'abcd'。如果同时读取'abcd',则此代码正常运行,日志显示为:

BluetoothService: BEGIN mConnectedThread
BluetoothService: abcd
BluetoothHandler: abcd

但是如果连续的数据块被分成两部分读取,则第二组数据在到达处理程序时会覆盖第一组数据。这是我见过的一些示例日志。

BluetoothService: BEGIN mConnectedThread
BluetoothService: a
BluetoothService: bcd
BluetoothHandler: b
BluetoothHandler: bcd

或者:

BluetoothService: BEGIN mConnectedThread
BluetoothService: abc
BluetoothService: d
BluetoothHandler: dbc
BluetoothHandler: d

或者:

BluetoothService: BEGIN mConnectedThread
BluetoothService: ab
BluetoothService: cde
BluetoothHandler: cd
BluetoothHandler: cde

请注意,发送的第二条消息始终会覆盖第一条消息的数据,最多只能覆盖最短消息的长度。此外,在mHandler处理第一条消息之前,始终会发送这两条消息。

我猜我在第一个消息被完全处理之前,某个地方的公共缓冲区被覆盖了,但是我没看到。有什么建议吗?

3 个答案:

答案 0 :(得分:3)

创建消息对象时是否可能需要使用第二个字节缓冲区?

mHandler.obtainMessage(MESSAGE_READ, bytes, -1, copyOfBuffer)

我怀疑mHandler(虽然我不知道它是什么)是保留对你发送给他的字节数组的引用。

答案 1 :(得分:0)

我意识到这是一个老线程,但对后人来说......

我刚遇到同样的问题。巧合的是,它也是基于谷歌蓝牙聊天样本的代码。我的代码还谈到了一个蓝牙设备,其数据出现在'片段中。在mmInStream.read()中,导致许多小消息被发送到处理程序。

与原始海报一样,我发现邮件被覆盖了。我相信这是因为消息处理的android实现是一种轻量级消息传递机制。具体来说,.obtainMessage()从消息结构的全局池分配,以避免运行时分配。为了符合这一要求,他们不要复制消息中包含的数据(/ OBJECT),而只是维护指向原始数据的指针。每个消息对象都包含msg.arg1中的字节数。在示例代码中,如果在接收处理程序处理完消息之前更改了字节数组(' buffer')中的数据,则处理程序仍具有消息的原始大小(包含在msg.arg1中) ,但缓冲区数据已更新(/覆盖)。

我可以看到解决此问题的三种方法: 1.使用android推荐的机制(创建一个数据包,并使用.setdata()将其附加到消息)。我没试过这个,但我希望捆绑创建会导致数据被复制出缓冲区字节数组。 2.将新内存用于消息数据。这可以通过运行时分配(但与轻量级消息传递的意图相冲突),或者使用多个静态分配的缓冲区,并在它们之间循环。这两种方法都有问题。 3.在低级线程' run()'中执行收集,并仅发送已完成蓝牙消息的消息。

我选择了第三种方法。在我的例子中,蓝牙消息包含终止字符串,因此我使用字符串构建器来收集mmInStream.read()返回的字节,并且仅在检测到行尾时向处理程序发送消息。

答案 2 :(得分:0)

另一种方法是使用.hasMessages()检查处理程序队列中是否已存在任何具有相同名称的消息。例如:

public void run() {
     DebugLog.i("BluetoothService", "BEGIN mConnectedThread");
     byte[] buffer = new byte[1024];
     int bytes;
         // Keep listening to the InputStream while connected
     while (true) {
          //Check if there is no pending similar message on the queue
          if (!mHandler.hasMessages(Constants.MESSAGE_READ)) {
              try {
                // Read from the InputStream
                bytes = mmInStream.read(buffer);

                DebugLog.d("BluetoothService", new String(buffer, 0, bytes));
                 // Send the obtained bytes to the UI Activity
                 mHandler.obtainMessage(MESSAGE_READ, bytes, -1, buffer)
                          .sendToTarget();
              } catch (IOException e) {
                    DebugLog.e(TAG, "disconnected", e);
                    connectionLost();
                    break;
              }
          }
     }
}