Android蓝牙套接字 - 超时

4
我已经编写了一个用于连接外部配件的蓝牙API。 该API的设计方式是有一堆阻塞调用,例如getTimesetTimegetVolumesetVolume等等。 这些调用的工作方式是创建一个有效载荷以发送,并调用一个名为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的客户端,所以我不能使用这种方法。
你能推荐其他的替代方案吗? 谢谢!
2个回答

2
您说您不能使用Future<>方法,因为您想重新抛出异常,但实际上这是可能的。
大多数在线示例都使用带有原型public ? call()的Callable进行实现,但只需将其更改为public ? call() throws Exception,一切都会好起来:您将在theTask.get()调用中获得异常,并且可以将其重新抛出给调用者。
我个人曾经在Android上使用Executors处理蓝牙套接字超时。
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

为什么不尝试一些像这样的东西呢

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。这显然需要对现有代码进行一些重新设计,以保持计数器并在接收到中断时适当地调整读取缓冲区的大小。


这会不会在每次调用时强制执行6秒的延迟? - ekatz
不,如果读取在6秒超时之前完成,则读取线程会向调用wrappedSendAndReceive()的线程发送中断信号。 - cyngus

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接