问题 在Android上的蓝牙中从InputStream读取数据时出错


我正在使用一个Android应用程序,它使用蓝牙连接在我的Android智能手机和非Android蓝牙模块之间传输数据,使用SPP配置文件。我使用Android Developer网站的蓝牙聊天示例作为参考。

我已经成功地将两个设备相互连接,并将简单的字符串从智能手机发送到蓝牙模块。但是我在读取从模块发回的数据时遇到了一些错误。我使用以下代码,与蓝牙聊天示例完全相同,从InputStream读取数据

while (true) {
    try {
        // Read from the InputStream
        bytes = mmInStream.read(buffer);
        String str = new String(buffer);
        Log.i(TAG, "mmInStream - " + str);
        // Send the obtained bytes to the UI Activity
        mHandler.obtainMessage(BluetoothChat.MESSAGE_READ, bytes, -1, buffer)
                .sendToTarget();
    } catch (IOException e) {
        Log.e(TAG, "disconnected", e);
        connectionLost();
        break;
    }
}

当我的蓝牙模块向手机发送一个简单的字符串时,没有正确接收该字符串。它以随机方式分成几个部分。例如,如果我将三次“1234567890abcdef1234567890abcdef0123456789”发送到手机,Eclipse上的Logcat将记录这些:

mmInstream - 12345678910abcdef��������(continuing null)
mmInstream - 1��������(continuing null)
mmInstream - 2345678910abcdef0123456789��������(continuing null)

首次。在第二次和第三次传输数据时,它会收到差异:

mmInstream - 1234567891�������(continuing null)
mmInstream - 0abcdef012�������(continuing null)
mmInstream - 3456789���������(continuing null)

mmInstream - 1234567891����������������(continuing null)
mmInstream - 0abcdef0123456789������������(continuing null)

我不知道为什么会发生这种情况以及如何解决这个问题。如果以这样的任意方式接收数据,我无法获得必要的数据来处理。我怎样才能将它整合在一起?

任何帮助,将不胜感激。

非常感谢。


7970
2017-09-06 07:09


起源

sleep命令对我有用。使用以上作为补充保证。谢谢!那你如何在主UI中检索这个字符串呢? Handler mHandler = new Handler(){@ Override public void handleMessage(Message message){/ * String retrieval * /}} - OrangePeel
它应该是 new String(buffer, 0, bytes)。没有什么能保证您在一次阅读中收到完整的信息。您只需要解决必须解析字节流而不是缓冲区的问题。这并不难。 - user207421


答案:


我注意到你的代码有两件事:

  • 首先进一步发送到你的应用程序对你读取的缓冲区的引用并不总是一个好的解决方案:如果在此期间缓冲区被覆盖怎么办? 例如,在stackoverflow上查看此错误 您可以通过复制从蓝牙读取的数据(例如使用buffer.clone())来绕过这一点,或者如果您不喜欢使用太多内存,则可以使读取缓冲区成为循环读取缓冲区。

  • 您应该能够重新编译您的数据,即使它是在单独的数据包中接收(但在短时间内收到数据包)。例如,您可以创建启动/停止标志。它仍然取决于您通过蓝牙发送的对象类型...

现在一个可能的解决方案,如果之前的2个警告没用,那就是:

而不是调用.read的无限循环 - 阻塞调用 - 你可以这样做:

while(true) {
     if mmInStream.getAvailable()>0 { 
          -your read code here-
     }
     else SystemClock.sleep(100);
}

这是一个黑客,它有时可能仍然只读取消息的某些部分 - 但它将是非常罕见的!

如果有用,请投票/更正!


9
2017-09-06 10:40



得到“bytes = mmInStream.read(buffer);”的时间到了。也参与我的实施。但是你的睡眠方法效果很好,不太确定为什么我可以使用Blue Chat作为基础来摆脱锁定。 - radix07
很高兴听到它有效 - 这基本上是一个黑客攻击,它仍然可能会产生问题。基本上,他们应该使用minLen和maxTimetout变量实现read()方法,就像任何其他套接字一样。但是我们可以自己做,或者做睡眠黑客。 - Radu
阻塞读取调用和阻塞睡眠之间没有区别,除了睡眠将在一半时间内阻塞太长时间,并且旋转循环将浪费CPU周期。 - user207421


我有这个问题,我已经用这种方式解决了这个角色的问题

public void run() {        
    int bytes; // bytes returned from read()
    int availableBytes = 0;        
    // Keep listening to the InputStream until an exception occurs
    while (needRun) {
        try {
            availableBytes = mmInStream.available();
            if(availableBytes > 0){
                byte[] buffer = new byte[availableBytes];  // buffer store for the stream
                // Read from the InputStream


                bytes = mmInStream.read(buffer);
                Log.d("mmInStream.read(buffer);", new String(buffer));
                if( bytes > 0 ){                        
                    // Send the obtained bytes to the UI activity
                    mHandler.obtainMessage(MESSAGE_READ, bytes, -1, buffer).sendToTarget();                     
                }                                   
            }
        } catch (IOException e) {
            Log.d("Error reading", e.getMessage());
            e.printStackTrace();
            break;
        }
    }
}

3
2018-01-13 00:51



不要复制粘贴这个代码在你的项目中,因为它在每个while循环迭代时分配一个新的bytearray,这可能会引入一个巨大的内存开销并让GC罢工疯狂 - Louis CAD
不要使用此代码,因为它是一个硬旋转循环,因为它在缓冲区的末尾记录垃圾,因为它通常完全浪费时间和空间。 - user207421


Radu的修复工作太棒了!我自己一直在使用蓝牙聊天示例代码处理这个问题很长一段时间。以下是我用来捕捉和显示远程传感器的温度读数:

  // Keep listening to the InputStream while connected
        while (true) {

             try {
                 byte[] buffer = new byte[128];
                 String readMessage;
                 int bytes;
                if (mmInStream.available()>2) { 
                    try {
                       // Read from the InputStream
                        bytes = mmInStream.read(buffer); 
                        readMessage = new String(buffer, 0, bytes);

                       }catch (IOException e) {
                        Log.e(TAG, "disconnected", e);
                        break;
                    }
                     // Send the obtained bytes to the UI Activity
                     mHandler.obtainMessage(HomeBlueRemote.MESSAGE_READ, bytes, -1, readMessage)
                          .sendToTarget();
        }
        else {
            SystemClock.sleep(100);
               }
            } catch (IOException e) {

                e.printStackTrace();
            }

       }

    }

正如你所见,我将(buffer.available()> 0)修改为> 2.这是因为我的微控制器正在为温度发送2个字节。 。在此修复之前,字节计数的输入流会有所不同,有时只捕获1个字节,这会搞砸Android应用程序中的温度显示读数。再次,在网络上的所有建议,Radu有android输入流bug的最佳解决方法。


0
2017-11-24 10:20



定义缓冲区的问题在于,如果有更多数据进入,则会被截断。但对于我来说,定义一个比4096更大的缓冲区崩溃了应用程序:(我得到了这个东西我不能将缓冲区拆分为格式化的响应,有时数据位非常大并且拆分它意味着我在解析中放松了我的位置: ( - ppumkin
@ppumkin如果有更多数据,下次还是可以读取。它没有被截断。 - user207421


我找到了!

你必须重置缓冲区:

buffer = new byte [1024];

在这之前:

bytes = mmInStream.read(buffer); 

至少它对我有用,并且不需要睡眠命令。


0
2018-06-26 21:24



不,你没有。你只需要使用 new String(buffer, 0, bytes) 而不是将整个缓冲区转换为String,忽略读取计数。 - user207421


我认为有很多好的答案。我的贡献是在进入蓝牙输入流时一次处理一个数字,如下所示。这个值的目的是确保每组数据都是一个长度值(作为String),通过将每个新值(包含在主Activity Handler中的readMessage字符串中)放入List中,可以非常容易地处理它。这样,你就可以使用List(String)(编辑器不会让我使用<>的)对象通过提取via来将数据处理回实数

Integer.valueOf(此处传递的List(String)对象)

//连接时继续收听InputStream         while(true){

         try {
             byte[] buffer = new byte[1]; // make this one byte to pass one digit at a time through Bluetooth Handler
             String readMessage;
             int bytes;
            if (mmInStream.available()>2) { 
                try {
                   // Read from the InputStream
                    bytes = mmInStream.read(buffer); 
                    readMessage = new String(buffer, 0, bytes);

                   }catch (IOException e) {
                    Log.e(TAG, "disconnected", e);
                    break;
                }
                 // Send the obtained bytes to the UI Activity
                 mHandler.obtainMessage(HomeBlueRemote.MESSAGE_READ, bytes, -1, readMessage)
                      .sendToTarget();
    }

0
2018-04-21 07:07





我使用了最后两个代码,它们运行良好,但是当连接丢失时,用户界面不会得到通知,因此状态不会更改为STATE_NONE。在我的情况下,我希望应用程序尝试在连接丢失时重新连接最后一个设备!在尝试了很多方法后,我终于以这种方式解决了问题:

  1. 我创建了一个END字符,这个字符我知道我永远不会使用,但只是在我发送的字符串的末尾。 “”是我的字符串字符的结尾。
  2. 我创建了一个临时字符串tmp_msg。
  3. 每次收到Stream时,都会从该Stream中创建第二个临时字符串“readMessage”。
  4. 搜索“readMessage”以查找结束字符(“。”)。
  5. 如果未检测到结束字符,则“readMessage”将连同到tmp_msg。因此,当第一次和第二次和第三次没有收到结束字符时tmp_msg = readMessage1 + readMessage2 + readMessage3。
  6. 当结束字符“。”检测到tmp_msg与最后一个readMessage同步,并从中构造字节“buffer”。
  7. 然后将“buffer”发送到用户界面,并将tmp_msg重新初始化为“”(空字符串)。 这是整个代码: (不对BluetoothChat活动进行修改)。

    public void run() { 
        Log.i(TAG, "BEGIN mConnectedThread");
        byte[] buffer = new byte[1024];
        int bytes;
        // Keep listening to the InputStream while connected
        String tmp_msg =""; 
        while (true) {
            try {
                // Read from the InputStream
                bytes = mmInStream.read(buffer);
                String readMessage = new String(buffer, 0,bytes);
                if (readMessage.contains(".")){
                    tmp_msg+=readMessage;
                    byte[] buffer1 = tmp_msg.getBytes();
                    int bytes1=buffer1.length;
                    tmp_msg="";
                    // Send the obtained bytes to the UI Activity
                    mHandler.obtainMessage(BluetoothChat.MESSAGE_READ,bytes1,-1,buffer1).sendToTarget();
                }else{
                    tmp_msg+=readMessage;
                }
            } catch (IOException e) {
            //  Log.e(TAG, "disconnected", e);
                connectionLost();
                // Start the service over to restart listening mode
                BluetoothChatService.this.start();
                break;
            }
        }
    }
    

0
2017-07-14 10:08



如果您有新问题,请点击以查看 问问题 按钮。如果有助于提供上下文,请包含此问题的链接。 - 来自评论 - Jon


尝试这个:

public void run() {
        byte[] buffer;
        ArrayList<Integer> arr_byte = new ArrayList<Integer>();
        while (true) {
            try {
                int data = mmInStream.read();
                if(mmInStream.available()>0) {
                    arr_byte.add(data);
                } else {
                    arr_byte.add(data);
                    buffer = new byte[arr_byte.size()];
                    for(int i = 0 ; i < arr_byte.size() ; i++) {
                        buffer[i] = arr_byte.get(i).byteValue();
                    }
                    Log.e("INPUT",new String(buffer));
                    mHandler.obtainMessage(MESSAGE_READ, bytes, -1, buffer)
                            .sendToTarget();
                    arr_byte = new ArrayList<Integer>();
                }
            } catch (IOException e) {
                break;
            }
        }
    }

我也有这个问题。现在工作得很好。没有 字符。


-1
2017-12-21 18:10



注意Integer ArrayList中的自动装箱 - Louis CAD
你忘了检查流的结束。 - user207421