Подтвердить что ты не робот

Создание потока, который отменяет вызов InputStream.read(), если прошло время "x"

В настоящее время у меня есть рабочий поток ввода-вывода от Android BluetoothChat Example, но у вас проблемы. Мое приложение подключается через bluetooth к модулю bluetooth, который, в свою очередь, посылает сигнал на устройство, к которому физически подключен модуль.

Моя программа вызывает read() во входном потоке, и если отправляются данные, программа выполняется без проблем. Однако способ реализации потока не защищает от прерывания соединения. Если модуль физически удален из устройства или если устройство не отправляет никаких сигналов, мой код просто сидит и ждет при вызове InputStream.read().

Мой вызов read() выглядит следующим образом:

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

Когда моя программа действует корректно, оба вызова Log в блоке try возвращают значения 0 для mmInStream.available(). Когда входной поток прерывается, начальный вызов Log возвращает a 0, а второй никогда не вызывается. Затем моя программа заканчивается сбой до того, как будет достигнут блок catch.

Я искал несколько дней, чтобы исправить это, и нашел множество решений, но они либо не работали, либо я их не понимаю.

1) Использование сканера для InputStream показано ниже. Это не помогло, а также время при чтении.

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) Я попытался запустить Thread, который отменил бы вызов read после X-го времени, но он не сработает правильно:

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

После того, как эти два параметра не сработали, я пытался заглянуть в Java NIO или AsyncTask, но все это похоже на слишком много материала для добавления для распознавания тайм-аута ввода-вывода. Я также видел, что некоторые Sockets поддерживают функцию тайм-аута с использованием .setSoTimeout(), однако это BluetoothSocket, и из того, что я нашел, они не поддерживают эту функцию.

Поскольку нет класса I/O, который поддерживает метод read(), который принимает длину таймаута как параметр или время ожидания вообще, мне кажется, что добавление Thread было бы самой простой реализацией. Это неправильно? Любая информация о том, что я делаю неправильно с вышеуказанными методами, или как включить Java NIO/AsyncTask, будет с благодарностью.

EDIT:

Это новый код потока, который я пробовал, я в настоящее время меняю его на то, что показывает данный ответ и пытается это сделать. Я отправлю это, если он не работает после.

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

        }

ИЗМЕНИТЬ 2:

Я попробовал ответить Dheerej ниже. Я получаю IllegalMonitorStateException в вызове функции wait(). Я попытался, как было показано в ответе, а затем попробовал myThread.wait() вместо Thread.currentThread.wait(). Я предполагаю, что это исключение выбрасывается, потому что это объект myThread создается и запускается в другом потоке. Во всяком случае, приведенный ниже код почти идентичен ответу 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()
            }

Это результирующий LogCat. Ошибка говорит, что она начинается в строке 533, которая является вызовом wait() выше:

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
4b9b3361

Ответ 1

Попробуйте сначала:

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) {
    ...
}

В исходном коде вы вызываете available() до read(), обычно нет ожидающих чтения данных. Затем вы вызываете read(), который блокирует и ждет данных, затем читает все это. Затем вы вызываете available() снова, и еще раз нет данных, потому что все было прочитано:) Лучше: спящий режим до available() возвращает ненулевое значение, затем читайте. Однако это может не сработать, поскольку available() всегда разрешено возвращать 0 (даже если данные действительно доступны).

Если вышеуказанное не работает, попробуйте технику из этого вопроса: Можно ли читать из InputStream с таймаутом?

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) {
    ...
}

Наконец, в документах BluetoothSocket говорится, что вы можете закрыть сокет из любого потока и вступить в силу немедленно. Таким образом, вы можете просто иметь контрольный поток, и если вызов чтения не удалось вызвать close() в сокете, что приведет к возврату заблокированного read() с ошибкой. Это было то, что предложил Dheeraj выше, но вам нужно только позвонить close(), когда другой поток застрял (из-за ошибки сети/потеря связи /etc ): в противном случае просто проверяйте его ход один раз в то время, но не закрывайте пока ваше чтение не зашло слишком долго.

Это, безусловно, выглядит как нехватка тайм-аутов (и невозможность прерывания заблокированного чтения() извне) долгое время была главной продолжающейся болью в Java.

См. также:

Можно ли читать из InputStream с тайм-аутом? (использует Callable/Future)

Можно ли установить тайм-аут для функции read() для ввода InputStream? (используется Socket.setSoTimeout())

Как убить вызов BufferedInputStream.read() (использует InterruptibleChannel)

Как остановить поток, ожидающий операции блокировки чтения в Java?

Ответ 2

Попробуйте этот код, который расширяется в моем комментарии выше:

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()
    }
}