简体   繁体   English

Android BluetoothSocket - 超时

[英]Android BluetoothSocket - Timing out

I have written a Bluetooth API for connecting with an external accessory.我写了一个蓝牙 API 用于连接外部配件。 The way that the API is designed is that there are a bunch of blocking calls such as getTime , setTime , getVolume , setVolume , etc. The way these work is that they create a payload to send and call a method called sendAndReceive() which does some prep work and eventually does the following: 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;

The problem is that sometimes this device becomes slow or non-responsive so I would like to put a timeout on this call.问题是有时这个设备会变得很慢或没有响应,所以我想在这个调用上设置一个超时。 I have tried several methods of putting this code in a thread\future task and running it with a timeout, for example:我尝试了几种方法将此代码放入线程\未来任务并超时运行,例如:

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

The problem with this approach is that I can't re-throw exceptions in the call method and since some of the methods throw exceptions I want to forward back to the API's client I can't use this methodology.这种方法的问题是我不能在调用方法中重新抛出异常,并且由于某些方法抛出异常我想转发回 API 的客户端,所以我不能使用这种方法。

Can you recommend some other alternative?你能推荐一些其他的选择吗? Thanks!谢谢!

You're saving you can't use the Future<> method because you want to re-throw the exception but in fact this is possible.您正在保存您不能使用 Future<> 方法,因为您想重新抛出异常,但实际上这是可能的。

Most examples online do implement Callable with the prototype public? call()大多数在线示例都使用public? call() public? call() but just change it to public? call() throws Exception public? call()但只是将其更改为public? call() throws Exception public? call() throws Exception and all will be fine: you'll get the exception in the theTask.get() call and you can rethrow it to callers. public? call() throws Exception ,一切都会好起来的:你会在 theTask.get() 调用中得到异常,你可以将它重新抛出给调用者。

I have personally used Executors exactly for bluetooth socket timeout handling on android:我个人使用 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);
    }
}

Why not try something like为什么不尝试类似的东西

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

There is an edge case here that the Thread calling wrappedSendAndReceive() may get interrupted for some reason other than the interrupt from the ReadTask.这里有一个边缘情况,调用wrappedSendAndReceive()的线程可能会由于 ReadTask 的中断以外的某种原因被中断。 I suppose a done bit could be added to the ReadTask to allow the other thread to test if the read finished or the interrupt was caused by something else, but I'm not sure how necessary this is.我想可以将完成位添加到 ReadTask 以允许其他线程测试读取是否完成或中断是由其他原因引起的,但我不确定这是多么必要。

A further note is that this code does contain the possibility for data loss.进一步注意的是,此代码确实包含数据丢失的可能性。 If the 6 seconds expires and some amount of data has been read this will end up being discarded.如果 6 秒到期并且已经读取了一定数量的数据,这将最终被丢弃。 If you wanted to work around this, you'd need to read one byte at a time in ReadTask.run() and then appropriately catch the InterruptedException.如果您想解决这个问题,您需要在 ReadTask.run() 中一次读取一个字节,然后适当地捕获 InterruptedException。 This obviously requires a little rework of the existing code to keep a counter and appropriately resize the read buffer when the interrupt is received.这显然需要对现有代码进行一些修改,以保留一个计数器并在收到中断时适当地调整读取缓冲区的大小。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM