2011-06-14 46 views
4

我寫了一個藍牙API用於連接外部附件。 該API的設計方式是,有一幫阻塞調用,如getTimesetTimegetVolumesetVolume等 的方式,這些工作是他們創造一個有效載荷發送和呼叫被叫sendAndReceive()方法,做一些準備工作,並最終執行以下操作:Android BluetoothSocket - 定時輸出

byte[] retVal = null; 
BluetoothSocket socket = getSocket(); 
// write 
socket.getOutputStream().write(payload); 
// read response 
if(responseExpected){ 
    byte[] buffer = new byte[1024]; // buffer store for the stream 
    int readbytes = socket.getInputStream().read(buffer); 
    retVal = new byte[readbytes]; 
    System.arraycopy(buffer, 0, retVal, 0, readbytes); 
} 
return retVal; 

的問題是,有時這種設備變慢或不響應的,所以我想提出一個超時在此調用。 我試圖把這個代碼在一個線程中\未來的任務,並與超時運行它,例如幾種方法:

FutureTask<byte[]> theTask = null; 
// create new task 
theTask = new FutureTask<byte[]>(
     new Callable<byte[]>() { 

      @Override 
      public byte[] call() { 
       byte[] retVal = null; 
       BluetoothSocket socket = getSocket(); 
       // write 
       socket.getOutputStream().write(payload); 
       // read response 
       if(responseExpected){ 
        byte[] buffer = new byte[1024]; // buffer store for the stream 
        int readbytes = socket.getInputStream().read(buffer); 
        retVal = new byte[readbytes]; 
        System.arraycopy(buffer, 0, retVal, 0, readbytes); 
       } 
       return retVal; 
      } 
     }); 

// start task in a new thread 
new Thread(theTask).start(); 

// wait for the execution to finish, timeout after 6 secs 
byte[] response; 
try { 
    response = theTask.get(6L, TimeUnit.SECONDS); 
} catch (InterruptedException e) { 
    throw new CbtException(e); 
} catch (ExecutionException e) { 
    throw new CbtException(e); 
} catch (TimeoutException e) { 
    throw new CbtCallTimedOutException(e); 
} 
    return response; 
} 

這種方法的問題是在,我不能重新拋出異常調用方法,並且由於某些方法會拋出異常,我想將其轉發回API客戶端,因此我無法使用此方法。

你能推薦一些其他的選擇嗎? 謝謝!

回答

1

爲什麼不嘗試像

public class ReadTask extends Thread { 
    private byte[] mResultBuffer; 
    private Exception mCaught; 
    private Thread mWatcher; 
    public ReadTask(Thread watcher) { 
    mWatcher = watcher; 
    } 

    public void run() { 
    try { 
     mResultBuffer = sendAndReceive(); 
    } catch (Exception e) { 
     mCaught = e; 
    } 
    mWatcher.interrupt(); 
    } 
    public Exception getCaughtException() { 
    return mCaught; 
    } 
    public byte[] getResults() { 
    return mResultBuffer; 
    } 
} 

public byte[] wrappedSendAndReceive() { 
    byte[] data = new byte[1024]; 
    ReadTask worker = new ReadTask(data, Thread.currentThread()); 

    try { 
    worker.start(); 
    Thread.sleep(6000); 
    } catch (InterruptedException e) { 
    // either the read completed, or we were interrupted for another reason 
    if (worker.getCaughtException() != null) { 
     throw worker.getCaughtException(); 
    } 
    } 

    // try to interrupt the reader 
    worker.interrupt(); 
    return worker.getResults; 
} 

這裏有一個邊緣的情況下,該線程調用wrappedSendAndReceive()可能會中斷比從ReadTask中斷其他一些原因。我想可以將一個完成位添加到ReadTask中,以允許另一個線程測試讀取完成或中斷是否由其他事件引起,但我不確定這是多麼必要。

還有一點需要注意的是,該代碼確實包含數據丟失的可能性。如果6秒過期並讀取了一些數據量,則最終將被丟棄。如果你想解決這個問題,你需要在ReadTask.run()中一次讀取一個字節,然後恰當地捕獲InterruptedException。這明顯需要對現有代碼進行一些修改,以保留一個計數器,並在接收到中斷時適當調整讀取緩衝區的大小。

+0

沒有這股力量在每次調用6秒的延遲? – ekatz 2011-06-23 14:45:28

+0

不,如果讀取在6秒超時之前完成,則讀取器Thread向調用wrappedSendAndReceive()的Thread發送一箇中斷。 – cyngus 2011-06-23 15:10:08

2

您正在保存您無法使用未來<>方法,因爲您想重新拋出異常,但實際上這是可能的。

大多數在線的例子就實現贖回與原型public ? call()但只是將其更改爲public ? call() throws Exception,一切都會好起來:你會得到在theTask.get()調用的異常,並可以將其重新拋出給調用者。

我曾親自使用完全相同的藍牙套接字超時處理在Android執行人:

protected static String readAnswer(...) 
throws Exception { 
    String timeoutMessage = "timeout"; 
    ExecutorService executor = Executors.newCachedThreadPool(); 
    Callable<String> task = new Callable<String>() { 
     public String call() throws Exception { 
      return readAnswerNoTimeout(...); 
     } 
    }; 
    Future<String> future = executor.submit(task); 
    try { 
     return future.get(SOCKET_TIMEOUT_MS, TimeUnit.MILLISECONDS); 
    } catch (TimeoutException ex) { 
     future.cancel(true); 
     throw new Exception(timeoutMessage); 
    } 
}