2014年2月23日 星期日

「已解決」Android 自帶範例中的 BluetoothChat 會分段讀取的情況

Android 自帶的藍芽範例BluetoothChat很好用,
而且想因此學習bluetooth的原理與處理機制可以藉由這隻範例程式來研究,
但是我在測試Android  這隻BluetoothChat與其他裝置連線時(stm32f4 discovery) ,

問題來了:
  • 發現在stm32f4 送一段簡單的訊息 --> printf("This is a bug \r\n" );
而BluetoothChat接收端會有分段讀取的現象,例如一段message會拆成
          Thi 
          s is a b
          ug

          即使BluetoothChat 內部本身已經有處理掉char 與 byte的型別轉換,但是重點不在於這  
         裡,先來看BluetoothChat核心讀取端的程式碼:

public void run() {
            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);

                    // Send the obtained bytes to the UI Activity
                    mHandler.obtainMessage(BluetoothChat.MESSAGE_READ, bytes, -1, buffer)
                            .sendToTarget();
                } catch (IOException e) {
                    //Error handling removed to simplify bug report
                }
            }
        }





首先先注意到這一行:

bytes = mmInStream.read(buffer);


如果你 Log 出 bytes , 你會發現就算你的other device傳送過來的指令都是一模一樣的字數,比如說固定就是傳10個char , 那你每一次在Android 這邊收到的bytes的字數都會不一樣,
這就是會分段的原因所在,所以當然你在Android 抓取一次的結果就是不完整的。

引述  Android Open Source Project - Issue Tracker

裡面提到了問題所在:
The problem is that "buffer" is reused, but obtainMessage(...) isn't guaranteed to be done with buffer by the time more data is available from the stream, and no defensive copy has been made of the buffer.


他的解決方法是將buffer定義在迴圈裡面,讓他每進入此迴圈都重新定義一個新的buffer,但是這不但會浪費多的資源,重點是我試了也沒用........還是會分段,所以再換別的方法。


我認為應該是要在迴圈內部裡面等待一段訊息全部傳完並且乾淨,才能夠當作有效的一段訊息,因此我修改程式如下:

public void run() {
            byte[] buffer = new byte[1024];
            int bytes;
            

            // Keep listening to the InputStream while connected
            while (true) {
                try {
                    String msg="";
                    
                    while(mmInStream.available()==0){
                        /*wait the message buffer*/
                    }
                    while (true){
                         // Read from the InputStream
                         bytes = mmInStream.read(buffer);
                                                  String temp_msg = new String(buffer,0,bytes);
                         msg+=_temp_msg;
                         if(mmInStream.available()==0)break;
                    }
                   
                    // Send the obtained bytes to the UI Activity
                    mHandler.obtainMessage(BluetoothChat.MESSAGE_READ, bytes, -1, msg)
                            .sendToTarget();
                } catch (IOException e) {
                    //Error handling removed to simplify bug report
                }
            }
        }



或者是這樣也可以,這個版本比較穩定(參照Ref  3),
只是我額外將格式改成了 ISO-8859-1 ,Code如下:

 public void run() {
        int bytes;
        byte[] buffer = new byte[1024];
        String end = "\n";
        StringBuilder curMsg = new StringBuilder();

        while (true){
            try {
                while (-1 != (bytes = mmInStream.read(buffer))) {
                    curMsg.append(new String(buffer, 0, bytes, Charset.forName("ISO-8859-1")));
                    int endIdx = curMsg.indexOf(end);
                    if (endIdx != -1) {
                        String fullMessage = curMsg.substring(0, endIdx + end.length());
                        curMsg.delete(0, endIdx + end.length());

                        // Now send fullMessage
                        // Send the obtained bytes to the UI Activity
                        mHandler.obtainMessage(ControlEPW_Fragment.MESSAGE_READ, bytes, -1, fullMessage)
                                .sendToTarget();
                    }
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

    }

上面的 end = "\n";   代表當我接收到一大串字串,只要收到了換行符號,就會送出去String給主Handler。


但是這裡要注意到我跟原始程式不同的是,原始程式傳出去是以byte[] object 傳出去主Handler ,而我是已String方式傳出去主Handler ,所以我在主Handler不用再轉一次String了 ,因此我把主Handler那裡將message 的 byte[] to String那一段拿掉,我想這不用在多說了。


附帶一提: 此修改程式在我的case下成功的,不會分段,若是有其他問題可以再回報與討論,Thanks.









9 則留言:

  1. 你好:
    請問是以byte[] object 傳出去Handler
    那我該要如何判斷結尾呢??

    回覆刪除
    回覆
    1. 抱歉,這麼晚才回應,如果你去看原始程式,他是以byte[] 傳到主Handler,然後在主Handler那邊轉為String,而我是在還沒傳出去就先轉了String。

      至於你說的判斷結尾,我是當收到換行符號"\n",我就傳出去Handler,當成是一段有效的訊息。

      刪除
    2. 都是指令的傳遞。
      要如何去判斷是否為結尾呢???

      刪除
  2. 請問 關於接收資料的時候
    很多資料會喪失還有重複傳送同一個字
    會是那些問題呢

    回覆刪除
  3. 你是照著上面的code去執行的嗎?

    回覆刪除
  4. 你好,關於主Handler那裡,你說不需要在轉成String?
    case MESSAGE_READ:
    byte[] readBuf = (byte[]) msg.obj;
    String readMessage = new String(readBuf, 0, msg.arg1);
    if(readMessage=="BT1015"){ //我想判斷藍牙端送來的字串
    digitalInBtn.setChecked(false);
    }else{
    digitalInBtn.setChecked(true);
    }
    break;
    要改哪裡?

    回覆刪除
  5. 請問主程式那個case MESSAGE_READ:
    那段要怎改啊?

    回覆刪除
  6. 你好~
    我用第一個範例去修改
    主Handler那邊也改好了
    可是會變成收不到值

    回覆刪除
  7. ControlEPW_Fragment這個有錯誤,要怎麻修改呢?不好意思

    回覆刪除