Android BluetoothSocket - out timing
-
27-10-2019 - |
Question
J'ai écrit une API Bluetooth pour la connexion avec un accessoire externe.
La façon dont l'API est conçu est qu'il ya un tas de blocage des appels tels que getTime
, setTime
, getVolume
, setVolume
, etc.
La façon dont ces travaux est qu'ils créent une charge utile pour envoyer et appeler une méthode appelée sendAndReceive()
qui fait un travail de préparation et fait finalement ce qui suit:
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;
Le problème est que, parfois, cet appareil devient lent ou non réactif, donc je voudrais mettre un délai d'attente sur cet appel. J'ai essayé plusieurs méthodes de mettre ce code dans un thread \ tâche future et en cours d'exécution avec un délai d'attente, par exemple:
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;
}
Le problème avec cette approche est que je ne peux pas exception re-jeter dans la méthode d'appel et depuis certaines des méthodes lancer des exceptions que je veux transmettre de nouveau au client de l'API, je ne peux pas utiliser cette méthode.
Pouvez-vous recommander une autre alternative? Merci!
La solution
Pourquoi ne pas essayer quelque chose comme
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;
}
Il y a un cas limite ici que le thread appelant wrappedSendAndReceive()
peut être interrompue pour une raison autre que l'interruption du ReadTask. Je suppose un peu fini pourrait être ajouté à la ReadTask pour permettre à l'autre fil à tester si la lecture terminée ou l'interruption a été provoquée par autre chose, mais je ne suis pas sûr nécessaire c'est.
Une autre note est que ce code ne contient la possibilité de perte de données. Si les 6 secondes arrive à expiration et une certaine quantité de données a été lu cela va finir par être mis au rebut. Si vous voulez travailler autour de cela, vous aurez besoin de lire un octet à la fois dans ReadTask.run () et puis prendre de manière appropriée le InterruptedException. Cela nécessite évidemment un peu réusinage du code existant pour maintenir un compteur et redimensionner de façon appropriée la mémoire tampon de lecture lorsque l'interruption est reçue.
Autres conseils
Vous enregistrez vous ne pouvez pas utiliser la méthode Future <> parce que vous voulez à nouveau jeter l'exception mais en fait cela est possible.
La plupart des exemples font en ligne avec le mettre en œuvre appelable public ? call()
prototype mais juste changer à public ? call() throws Exception
et tout ira bien. Vous aurez l'exception dans le theTask.get () appel et vous pouvez réémettre à appelants
J'ai personnellement utilisé Huissiers exactement Bluetooth socket manipulation délai d'attente sur 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);
}
}