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

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

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

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

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处理第一条消息之前,始终会发送这两条消息。

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

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

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

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

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

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

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

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

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

另一种方法是使用.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; } } } }