我已经编写了一个用于连接外部附件的蓝牙API。
设计API的方式是有一堆阻止调用,例如getTime
,setTime
,getVolume
,setVolume
等。
这些工作的方式是,他们创建一个有效负载来发送和调用一个名为sendAndReceive()
的方法,该方法执行一些准备工作并最终执行以下操作:
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;
问题是有时候这个设备变慢或者没有响应,所以我想对这个调用设置超时。 我已经尝试了几种将此代码放在线程\ future任务中并使用超时运行它的方法,例如:
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的客户端,我不能使用这种方法。
你能推荐一些其他选择吗? 谢谢!
答案 0 :(得分:2)
您正在保存,您无法使用Future&lt;&gt;方法,因为你想重新抛出异常,但事实上这是可能的。
大多数在线示例都使用原型public ? call()
实现Callable,但只需将其更改为public ? call() throws Exception
,一切都会好的:您将在theTask.get()调用中获得异常,您可以把它重新扔给来电者。
我个人使用Executors完全用于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);
}
}
答案 1 :(得分: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。这显然需要对现有代码进行一些修改,以便在收到中断时保持计数器并适当调整读缓冲区的大小。