How to unblock InputStream.read() on Android?

18,122

Solution 1

Only call read() when there is data available.

Do something like that:

    while( flagBlock )
    {
        if( stream.available() > 0 )
        {
            stream.read( byteArray );
        }
    }

set the flagBlock to stop the reading.

Solution 2

See Java Concurrency In Practice for a really good system to cancel a thread when working with sockets. It uses a special executor (CancellingExecutor) and a special Callable (SocketUsingTask).

Solution 3

When the other end closes the connection your stream will return -1 on a read(). If you cannot trigger the other end to close the connection e.g. by closing your output stream, you can close the socket which will cause an IOException in the blocking read() thread.


Can you provide a short example which reproduces your problem?

ServerSocket ss = new ServerSocket(0);
final Socket client = new Socket("localhost", ss.getLocalPort());
Socket server = ss.accept();
Thread t = new Thread(new Runnable() {
    public void run() {
        int ch;
        try {
            while ((ch = client.getInputStream().read()) != -1)
                System.out.println(ch);
        } catch (SocketException se) {
            System.out.println(se);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
});
t.start();
server.getOutputStream().write("hi\n".getBytes());
Thread.sleep(100);
client.close();
t.join();

server.close();
ss.close();

prints

104
105
10
java.net.SocketException: Socket closed

Solution 4

I had such issue on Samsung 2.3. When switching from 3G to Wifi InputStream.read() method blocks. I tried all tips from this topic. Nothing helped. From my prospective this is device specific issue because it should throw IOException due to javadoc. My solution is to listen for android broadcast android.net.conn.CONNECTIVITY_CHANGE and close connection from another thread it will cause IOException in blocked thread.

Here is code example:

DownloadThread.java

private volatile boolean canceled;

private volatile InputStream in;

private boolean downloadFile(final File file, final URL url, long totalSize) {
    OutputStream out = null;
    try {
        Log.v(Common.TAG, "DownloadThread: downloading to " + file);

        in = (InputStream) url.getContent();
        out = new FileOutputStream(file);

        return copy(out, totalSize);
    } catch (Exception e) {
        Log.e(Common.TAG, "DownloadThread: Exception while downloading. Returning false ", e);
        return false;
    } finally {
        closeStream(in);
        closeStream(out);
    }
}

public void cancelDownloading() {
    Log.e(Common.TAG, "DownloadThread: cancelDownloading ");
    canceled = true;
    closeStream(in); //on my device this is the only way to unblock thread
}

private boolean copy(final OutputStream out, long totalSize) throws IOException {
    final int BUFFER_LENGTH = 1024;
    final byte[] buffer = new byte[BUFFER_LENGTH];
    long totalRead = 0;
    int progress = 0;
    int read;

    while (!canceled && (read = in.read(buffer)) != -1) {
        out.write(buffer, 0, read);
        totalRead += read;
    }
    return !canceled;
}

Solution 5

We were having the same issue: no exception when switching network (e.g. switching from 3G to WiFi while downloading).

We are using the code from http://www.androidsnippets.com/download-an-http-file-to-sdcard-with-progress-notification, which is working perfectly except in some cases when the network connection was lost.

The solution was specifying a timeout value, this is set standard to 0 (meaning: wait infinitely).

    HttpURLConnection c = (HttpURLConnection) u.openConnection();
    c.setRequestMethod("GET");
    c.setDoOutput(true);
    c.setReadTimeout(1000);
    c.connect();

Experiment with a timeout value appropriate for you.

Share:
18,122
tajmahal
Author by

tajmahal

Updated on June 12, 2022

Comments

  • tajmahal
    tajmahal almost 2 years

    I have a thread in which the read() method of an InputStream is called in a loop. When there are no more bytes to read, the stream will block until new data arrives.

    If I call close() on the InputStream from a different thread, the stream gets closed, but the blocked read() call still remains blocked. I would assume that the read() method should now return with a value of -1 to indicate the end of the stream, but it does not. Instead, it stays blocked for several more minutes until a tcp timeout occurs.

    How do I unblock the close() call?

    Edit:

    Apparently, the regular JRE will throw a SocketException immediately when the stream or socket the blocking read() call corresponds to is close()'d. The Android Java runtime which I am using, however, will not.

    Any hints on a solution for the Android environment would be greatly appreciated.

    • Smudge202
      Smudge202 almost 13 years
      I'm not a java guy, but if you control the app/thread sending down the stream, couldn't you send an End-of-message byte/couple of bytes which the reader can use to determine that the stream should be closed?
  • Vishy
    Vishy almost 13 years
    available() will only tell you if data is available without making a system call. This can return 0 when there is data to read.
  • tajmahal
    tajmahal almost 13 years
    Ok. That is exactly what I previously had. It is using a lot of cpu, though, as the loop is constantly spinning. I figured that using blocking I/O would remedy this problem.
  • tajmahal
    tajmahal almost 13 years
    That is what I thought. But I do not get an exception. The read() keeps blocking even though the socket is closed.
  • tajmahal
    tajmahal almost 13 years
    Thank you for the nice code sample! The sample is working fine when compiling it as a regular Java application. Putting it into a bare-bones Android application reveals that the exception is apparently not thrown, even though the very same java.net classes are used. So the problem lies within Android, I assume. Any idea?
  • Vishy
    Vishy almost 13 years
    I have taken the liberty of adding Android as a tag as this appears to be a key detail. ;)
  • Pedro Loureiro
    Pedro Loureiro almost 13 years
    you can add a sleep as the last line of your while loop. This will prevent you from busy waiting
  • tajmahal
    tajmahal almost 13 years
    This solution is not the most elegant one, but the only one that I got to work properly on Android.
  • Tom
    Tom about 12 years
    Note that the Android docs for available() start with the warning "Note that this method provides such a weak guarantee that it is not very useful in practice."