Hacer un hilo que cancela una llamada de InputStream.read () si ha pasado el tiempo 'x'

Actualmente tengo un flujo de E / S de trabajo del ejemplo BluetoothChat de Android, pero he tenido problemas. Mi aplicación se conecta vía bluetooth a un módulo bluetooth, que a su vez envía una señal a un dispositivo al que está físicamente conectado el módulo.

Mi programa llama a read() en un flujo de entrada, y si hay datos enviados, el programa se ejecuta sin problemas. Sin embargo, la forma en que se implementa el flujo no tiene protección contra una conexión interrumpida. Si el módulo se retira físicamente del dispositivo o si el dispositivo no envía ninguna señal, mi código simplemente se encuentra y espera en la llamada de InputStream.read() .

Mi llamada de read() ve así:

 try { Log.i( "1) I/O", "available bits: " + mmInStream.available() ); bytes = mmInStream.read(buffer, 0, length); Log.i( "2) I/O", "available bits: " + mmInStream.available() ); mHandler.obtainMessage(MainMenu.MESSAGE_READ, bytes, -1, buffer) .sendToTarget(); } catch (Exception e) { Log.i(TAG, "Catch Statement" ); Message msg = mHandler.obtainMessage(MainMenu.MESSAGE_TOAST); Bundle bundle = new Bundle(); bundle.putString( TOAST, "Device has disconnected from the Bluetooth Module." ); msg.setData(bundle); mHandler.sendMessage(msg); Log.e(TAG, "disconnected a", e); connectionLost(); // Start the service over to restart listening mode BluetoothService.this.start(); //break; } 

Cuando mi programa actúa correctamente, tanto las llamadas de Log en el bloque try devuelven valores de 0 para mmInStream.available() . Cuando se interrumpe el flujo de entrada, la llamada Log inicial devuelve un 0 y el segundo nunca se llama. Mi programa termina entonces chocando antes de que el bloque de la catch sea ​​cada alcanzado.

He estado buscando durante varios días para solucionar esto, y he encontrado numerosas soluciones, pero no han funcionado, o no las entiendo.

1) El uso de un escáner para el InputStream se muestra a continuación. Esto no proporcionó ninguna ayuda y también expiró durante la lectura.

 Scanner scan = new Scanner(new InputStreamReader(mmInStream)); scan.useDelimiter( "[\\r\\n]+" ); String readIn; try { readIn = scan.next(); scan = null; tempB = readIn.getBytes( Charset.forName( "US-ASCII" ) ); append = "\r\n".getBytes( Charset.forName( "US-ASCII" ) ); for( int i = 0; i < length; i++ ) { if( i == length - 1 ) { buffer[i] = append[1]; } else if ( i == length - 2 ) { buffer[i] = append[0]; } else { buffer[i] = tempB[i]; } } mHandler.obtainMessage(MainMenu.MESSAGE_READ, bytes, -1, buffer) .sendToTarget(); } catch (Exception e) { Log.i(TAG, "Catch Statement" ); Message msg = mHandler.obtainMessage(MainMenu.MESSAGE_TOAST); Bundle bundle = new Bundle(); bundle.putString( TOAST, "Device has disconnected from the Bluetooth Module." ); msg.setData(bundle); mHandler.sendMessage(msg); Log.e(TAG, "disconnected a", e); connectionLost(); // Start the service over to restart listening mode BluetoothService.this.start(); //break; } 

2) He intentado ejecutar un Thread que cancelaría la llamada de read después de X cantidad de tiempo, pero no funcionaría correctamente:

 public void run(int length) throws IOException { buffer = new byte[1024]; length1 = length; Thread myThread = new Thread(new Runnable() { public void run() { try { bytes = mmInStream.read( buffer, 0, length1 ); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }); synchronized (myThread) { myThread.start(); try { myThread.wait(500); if(myThread.isAlive()) { mmInStream.close(); Log.i( "InStream", "Timeout exceeded!"); } } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } try { myThread.run(); mHandler.obtainMessage(MainMenu.MESSAGE_READ, bytes, -1, buffer) .sendToTarget(); } catch (IOException e) { Message msg = mHandler.obtainMessage(MainMenu.MESSAGE_TOAST); Bundle bundle = new Bundle(); bundle.putString( TOAST, "Device has disconnected from the Bluetooth Module." ); msg.setData(bundle); mHandler.sendMessage(msg); connectionLost(); BluetoothService.this.start(); } 

Después de que estas dos opciones no funcionaron, he estado tratando de buscar Java NIO o AsyncTask , pero todo esto parece demasiado cosas para agregar para reconocer un tiempo de espera de E / S. También he visto que algunos Sockets apoyan una característica del timeout usando .setSoTimeout() , sin embargo esto es un BluetoothSocket y de lo que he encontrado ellos no apoyan esta característica.

Puesto que no hay ninguna clase de I/O que admita un método read() que toma una longitud de tiempo de espera como un parámetro, o tiempo de espera en absoluto, me parece que la adición de un hilo sería la implementación más simple. ¿Esto esta mal? Cualquier información sobre lo que estoy haciendo mal con los métodos anteriores, o cómo incorporar Java NIO / AsyncTask sería muy apreciada.

EDITAR:

Este es el nuevo código de hilo que he intentado, actualmente estoy cambiando a lo que la respuesta dada muestra y tratar de eso. Voy a publicar que si no funciona después.

 Thread myThread = new Thread(new Runnable() { public void run() { try { bytes = mmInStream.read( buffer, 0, length1 ); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }); synchronized (myThread) { try { myThread.wait(6000); Log.i( "InStream", "After wait" ); if(myThread.isAlive()) { Log.i( "InStream", "Timeout exceeded2!"); myThread.interrupt(); Log.i( "InStream", "Timeout exceeded!"); } else { myThread.interrupt(); } } catch (InterruptedException e) { // TODO Auto-generated catch block Log.i( "InStream", "Exception Caught" ); e.printStackTrace(); } } 

EDIT 2:

He intentado la respuesta Dheerej ha dado abajo. Recibo una IllegalMonitorStateException en la llamada de función wait() . Intenté como se demostró en la respuesta, entonces también intentado myThread.wait() vez de Thread.currentThread.wait() . Estoy asumiendo que esta excepción está siendo lanzada porque este es el objeto myThread está siendo creado y corrió dentro de otro hilo. De todos modos, el código de abajo es casi idéntico a Dheerej's respuesta Dheerej's .

  int length1 = length; Thread myThread = new Thread(new Runnable() { public void run() { buffer = new byte[1024]; try { bytes = mmInStream.read(buffer, 0, length1); } catch (IOException e) { e.printStackTrace(); } mHandler.obtainMessage(MainMenu.MESSAGE_READ, bytes, -1, buffer) .sendToTarget(); } }); myThread.start(); try { //Thread.currentThread().wait(500); myThread.wait( 1000 ); // Line 533 } catch (InterruptedException e) { e.printStackTrace(); //Log.i(TAG, "Catch Statement" ); Message msg = mHandler.obtainMessage(MainMenu.MESSAGE_TOAST); Bundle bundle = new Bundle(); bundle.putString( TOAST, "Device has disconnected from the Bluetooth Module." ); msg.setData(bundle); mHandler.sendMessage(msg); Log.e(TAG, "disconnected a", e); connectionLost(); // Start the service over to restart listening mode BluetoothService.this.start(); } if (myThread.isAlive()) { mmInStream.close(); // Alternatively try: myThread.interrupt() } 

Este es el LogCat resultante. El error dice que comienza en la línea 533, que es la llamada wait() anterior:

 12-28 17:44:18.765: D/BLZ20_WRAPPER(3242): blz20_wrp_poll: return 1 12-28 17:44:18.765: D/BLZ20_WRAPPER(3242): blz20_wrp_write: wrote 3 bytes out of 3 on fd 62 12-28 17:44:18.769: W/NATIVE CODE(3242): -4) baud9600=1, goodbaud=1 12-28 17:44:18.769: D/AndroidRuntime(3242): Shutting down VM 12-28 17:44:18.769: W/dalvikvm(3242): threadid=1: thread exiting with uncaught exception (group=0x40015578) 12-28 17:44:18.773: E/AndroidRuntime(3242): FATAL EXCEPTION: main 12-28 17:44:18.773: E/AndroidRuntime(3242): java.lang.IllegalMonitorStateException: object not locked by thread before wait() 12-28 17:44:18.773: E/AndroidRuntime(3242): at java.lang.Object.wait(Native Method) 12-28 17:44:18.773: E/AndroidRuntime(3242): at java.lang.Object.wait(Object.java:395) 12-28 17:44:18.773: E/AndroidRuntime(3242): at my.eti.commander.BluetoothService$ConnectedThread.run(BluetoothService.java:533) 12-28 17:44:18.773: E/AndroidRuntime(3242): at my.eti.commander.BluetoothService.read(BluetoothService.java:326) 12-28 17:44:18.773: E/AndroidRuntime(3242): at my.eti.commander.BluetoothService.changeitJava(BluetoothService.java:669) 12-28 17:44:18.773: E/AndroidRuntime(3242): at my.eti.commander.RelayAPIModel$NativeCalls.changeItJavaWrapper(RelayAPIModel.java:490) 12-28 17:44:18.773: E/AndroidRuntime(3242): at my.eti.commander.RelayAPIModel$NativeCalls.InitRelayJava(Native Method) 12-28 17:44:18.773: E/AndroidRuntime(3242): at my.eti.commander.MainMenu$1.handleMessage(MainMenu.java:547) 12-28 17:44:18.773: E/AndroidRuntime(3242): at android.os.Handler.dispatchMessage(Handler.java:99) 12-28 17:44:18.773: E/AndroidRuntime(3242): at android.os.Looper.loop(Looper.java:130) 12-28 17:44:18.773: E/AndroidRuntime(3242): at android.app.ActivityThread.main(ActivityThread.java:3687) 12-28 17:44:18.773: E/AndroidRuntime(3242): at java.lang.reflect.Method.invokeNative(Native Method) 12-28 17:44:18.773: E/AndroidRuntime(3242): at java.lang.reflect.Method.invoke(Method.java:507) 12-28 17:44:18.773: E/AndroidRuntime(3242): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:842) 12-28 17:44:18.773: E/AndroidRuntime(3242): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:600) 12-28 17:44:18.773: E/AndroidRuntime(3242): at dalvik.system.NativeStart.main(Native Method) 12-28 17:44:18.781: D/BLZ20_ASOCKWRP(3242): asocket_read 12-28 17:44:18.781: I/BLZ20_WRAPPER(3242): blz20_wrp_poll: nfds 2, timeout -1 ms 12-28 17:44:18.890: D/BLZ20_WRAPPER(3242): blz20_wrp_poll: transp poll : (fd 62) returned r_ev [POLLIN ] (0x1) 12-28 17:44:18.890: D/BLZ20_WRAPPER(3242): blz20_wrp_poll: return 1 12-28 17:44:18.890: D/BLZ20_WRAPPER(3242): blz20_wrp_read: read 5 bytes out of 5 on fd 62 

    Prueba esto primero:

     try { int available = 0; while (true) { int available = mmInStream.available(); if (available > 0) { break; } Thread.sleep(1); // here you can optionally check elapsed time, and time out } Log.i( "1) I/O", "available bits: " + available ); bytes = mmInStream.read(buffer, 0, length); Log.i( "2) I/O", "available bits: " + mmInStream.available() ); mHandler.obtainMessage(MainMenu.MESSAGE_READ, bytes, -1, buffer).sendToTarget(); } catch (Exception e) { ... } 

    En su código original, llama a available() antes de read() , normalmente no hay datos esperando para ser leídos. A continuación, llama a read() , que bloquea y espera los datos, luego lee todo. A continuación, llamar a available() nuevo y una vez más no hay datos, porque se ha leído 🙂 Mejor: dormir hasta available() devuelve distinto de cero, a continuación, leer. Sin embargo, esto puede no funcionar, ya que available() siempre se le permite devolver 0 (incluso si los datos están realmente disponibles).

    Si lo anterior no funciona, intente la técnica de esta pregunta: ¿Es posible leer de un InputStream con un tiempo de espera?

     Callable<Integer> readTask = new Callable<Integer>() { @Override public Integer call() throws Exception { return mmInStream.read(buffer, 0, length); } } try { Future<Integer> future = executor.submit(readTask); bytes = future.get(100, TimeUnit.MILLISECONDS); mHandler.obtainMessage(MainMenu.MESSAGE_READ, bytes, -1, buffer).sendToTarget(); } catch (TimeoutException e) { // deal with timeout in the read call } catch (Exception e) { ... } 

    Por último, los documentos BluetoothSocket dicen que puede cerrar el socket de cualquier subproceso y que tiene efecto inmediatamente. Así que podría simplemente tener un hilo de control y si la llamada de lectura no ha tenido éxito, llame a close() en el socket, lo que haría que el read() bloqueado regresara con un error. Esto fue lo que Dheeraj sugirió anteriormente, pero sólo tiene que llamar close() cuando el otro hilo está atascado (debido a un error de red / conexión perdida / etc): de lo contrario, sólo verifique su progreso de vez en cuando, pero no cierre como Siempre y cuando su lectura no ha tomado demasiado tiempo.

    Ciertamente, parece que la falta de tiempos muertos (y la imposibilidad de interrumpir una lectura bloqueada desde el exterior) ha sido un importante punto de dolor en Java durante mucho tiempo.

    Ver también:

    ¿Es posible leer de un InputStream con un tiempo de espera? (Usa Callable / Future )

    ¿Puedo establecer un tiempo de espera para la función read () de un InputStream? (Utiliza Socket.setSoTimeout() )

    Cómo matar una llamada .read () de BufferedInputStream (utiliza InterruptibleChannel )

    Cómo detener un hilo esperando en una operación de lectura de bloqueo en Java?

    Pruebe este código que se expande en mi comentario anterior:

     public void run(final int length) { Thread myThread = new Thread(new Runnable() { public void run() { buffer = new byte[1024]; try { bytes = mmInStream.read(buffer, 0, length); } catch (IOException e) { e.printStackTrace(); } mHandler.obtainMessage(MainMenu.MESSAGE_READ, bytes, -1, buffer) .sendToTarget(); } }); myThread.start(); try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } if (myThread.isAlive()) { mmInStream.close(); // Alternatively try: myThread.interrupt() } } 
    FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.