【问题标题】:Java threading issue with handler message data being overwritten by next message处理程序消息数据被下一条消息覆盖的 Java 线程问题
【发布时间】:2017-09-27 03:15:33
【问题描述】:

我有一个线程从蓝牙流中读取数据,该线程将数据发送到主 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 处理第一条消息之前发送。

我猜我是一个公共缓冲区,在第一条消息被完全处理之前,某处被覆盖,但我不知道在哪里。有什么建议吗?

【问题讨论】:

    标签: java android multithreading handler


    【解决方案1】:

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

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

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

    【讨论】:

    • 我刚写完帖子就想到了。我认为该消息只是获取对缓冲区的引用,因此处理程序获取已被后台线程覆盖的缓冲区..
    • 这接近于正确答案。两个线程同时使用同一个缓冲区对象,因此我必须实现一个锁定的缓冲区工厂以根据需要提供新的缓冲区。
    • “我怀疑 mHandler(虽然我不知道它是什么)保留了对您发送给他的那个字节数组的引用。”是的,你是绝对正确的@krico,谢谢你救了我的命!
    【解决方案2】:

    我意识到这是一个旧线程,但是为了后代......

    我刚刚遇到了同样的问题。巧合的是,它也是基于谷歌蓝牙聊天示例的代码。我的代码还与蓝牙设备通信,其数据出现在 mmInStream.read() 中的“sn-ps”中,导致向处理程序发送许多小消息。

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

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

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

    【讨论】:

      【解决方案3】:

      另一种方法是使用.hasMessages() 检查Handler 的队列中是否已经有任何同名的消息。示例:

      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;
                    }
                }
           }
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-04-16
        • 1970-01-01
        • 1970-01-01
        • 2013-03-06
        相关资源
        最近更新 更多