【问题标题】:Error in reading data from InputStream in Bluetooth on Android在 Android 上从蓝牙中的 InputStream 读取数据时出错
【发布时间】:2012-08-30 22:56:43
【问题描述】:

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

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

当我的蓝牙模块向手机发送一个简单的字符串时,该字符串没有被正确接收。它以随机方式分成几部分。例如,如果我向手机发送 3 次“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)

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

任何帮助将不胜感激。

非常感谢。

【问题讨论】:

  • 睡眠命令对我有用。使用上述作为额外的保证。谢谢!那么,您如何在主 UI 中检索此字符串? Handler mHandler = new Handler(){ @Override public void handleMessage(Message message){ /*字符串检索*/ } }
  • 应该是new String(buffer, 0, bytes)。没有什么可以保证您在一次阅读中就可以收到完整的消息。您只需要处理解析字节流而不是缓冲区的问题。这并不难。

标签: android bluetooth inputstream


【解决方案1】:

我注意到你的代码有两点:

  • 首先,向您的应用程序进一步发送对您读取的缓冲区的引用并不总是一个好的解决方案:如果同时缓冲区被覆盖怎么办? See this bug on stackoverflow for example 您可以通过复制从蓝牙读取的数据(例如使用 buffer.clone())来绕过此问题,或者如果您不喜欢使用太多内存,则可以将读取缓冲区设为循环缓冲区。

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

如果前面的两个警告没有用,现在一个可能的解决方案是:

您可以执行以下操作,而不是调用 .read 的无限循环(阻塞调用):

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

这是一种 hack,有时它仍可能只读取部分消息 - 但这种情况非常罕见!

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

【讨论】:

  • 获得“bytes = mmInStream.read(buffer);”的时间太长了也可以在我的实施中工作。但是你的 sleep 方法效果很好,不太清楚为什么我可以使用 Blue Chat 作为基础来解锁。
  • 很高兴听到它起作用了 - 它基本上是一个 hack,它仍然可能在某个时候产生问题。基本上,他们应该使用 minLen 和 maxTimetout 变量实现 read() 方法,就像任何其他套接字一样。但是我们可以自己做,或者做睡眠黑客。
  • 阻塞读调用和阻塞睡眠没有区别,只是睡眠会阻塞一半时间太长,自旋循环会浪费CPU周期。
  • 你救了我一些头痛!非常感谢您提供这个 hack/解决方案!
【解决方案2】:

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 inputstream 错误提供了最佳解决方法。

【讨论】:

  • 定义缓冲区的问题是,如果有更多数据进入,它会被截断。但是对我来说,定义比 4096 更大的缓冲区会使应用程序崩溃:( 我得到的这个东西是我无法将缓冲区作为格式化的响应进行拆分,有时数据位非常大,拆分它意味着我在解析中失去了位置: (
  • @ppumkin 如果有更多数据进入,它仍然可供下次读取。它不会被截断。
【解决方案3】:

我有这个问题,我已经解决了这个字符的问题 - 以这种方式

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

【讨论】:

  • 不要将这段代码复制粘贴到您的项目中,因为它会在每次 while 循环迭代时分配一个新的字节数组,这可能会带来很大的内存开销并让 GC 罢工像疯了一样
  • 不要使用此代码,因为它是一个硬自旋循环,因为它会在缓冲区末尾记录垃圾,而且通常完全浪费时间和空间。
【解决方案4】:

试试这个:

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

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

【讨论】:

  • 注意 Integer ArrayList 中的自动装箱
  • 您忘记检查流的结尾。
【解决方案5】:

我找到了!

你必须重置缓冲区:

buffer = new byte [1024];

在此之前:

bytes = mmInStream.read(buffer); 

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

【讨论】:

  • 不,你没有。您只需要使用new String(buffer, 0, bytes) 而不是将整个缓冲区转换为忽略读取计数的字符串。
【解决方案6】:

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

提取将数据处理回实数

Integer.valueOf(这里传入的List(String)对象)

// 连接时继续监听 InputStream 而(真){

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

【讨论】:

    【解决方案7】:

    我的方式:我在 MainActivity 中构建一个 textView,其 id 为 txtReceive。

    @Override
    public void run() {
        byte[] buffer=new byte[1024];
        int bytes;
        while(true) {
            try {
                bytes = inputStream.read(buffer);
                final String strReceived = new String(buffer, 0, bytes);
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        txtReceive.append(strReceived);
                    }
                });
            } catch (Exception e) {
                Log.d(TAG, "loi ");
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        txtReceive.setText("Error");
                    }
                });
            }
        }
    }
    

    【讨论】:

      【解决方案8】:

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

      1. 我创建了一个 END 字符,我知道我永远不会使用这个字符,但只是在我发送的字符串的末尾。 “。”是我的字符串字符的结尾。
      2. 我创建了一个临时字符串 tmp_msg。
      3. 每次收到流时,都会从该流中创建第二个临时字符串“readMessage”。
      4. “readMessage”搜索结束字符(“.”)。
      5. 如果未检测到结束字符,则将“readMessage”连接到 tmp_msg。所以当第一次和第二次和第三次没有收到结束字符时tmp_msg=readMessage1+readMessage2+readMessage3。
      6. 当结束字符“.”时检测到,tmp_msg 与 last readMessage 连接,并从中构造字节“缓冲区”。
      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;
                }
            }
        }
        

      【讨论】:

        【解决方案9】:

        我正在寻找解决这个问题的方法。 并找到了它: 发送可以重新加载的缓冲区是个坏主意。 所以我这样做了: (我发送的不是缓冲区,而是克隆的 RXbuffer)

        现在我对 1000 个块(每个块 32 个字节)有 0 个错误,而不是之前有 20% 的错误;

          public void run() {
                Log.i(TAG, "BEGIN mConnectedThread");
                byte[] buffer = new byte[1024];
                byte[] bufferRX = new byte[1024];
                int bytes;
        
                // Keep listening to the InputStream while connected
                while (mState == STATE_CONNECTED) {
                    try {
                        // Read from the InputStream
                        bytes = mmInStream.read(buffer);
                        bufferRX = buffer.clone();
                        
                        // Send the obtained bytes to the UI Activity
                        mHandler.obtainMessage(Constants.MESSAGE_READ, bytes, -1, bufferRX)
                                .sendToTarget();
                    } catch (IOException e) {
                        Log.e(TAG, "disconnected", e);
                        connectionLost();
                        break;
                    }
                }
            }
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2014-10-16
          • 1970-01-01
          • 2014-10-28
          • 1970-01-01
          • 1970-01-01
          • 2012-06-15
          • 1970-01-01
          相关资源
          最近更新 更多