如何在Android上取消InputStream.read()的阻塞?

7
我有一个线程,在其中循环调用InputStreamread()方法。当没有更多字节可读时,流会阻塞直到有新数据到达。
如果我从另一个线程调用InputStreamclose()方法,则该流将关闭,但被阻塞的read()调用仍然保持阻塞状态。我原以为read()方法现在应该返回值-1来表示流的结束,但它没有这样做。相反,它会一直阻塞几分钟,直到发生tcp超时。
如何解除close()调用的阻塞?
编辑:
显然,常规JRE会立即抛出SocketException,当与阻塞的read()调用对应的流或套接字被close()时。但我使用的Android Java运行时不会抛出此异常。
对于Android环境的解决方案提示将不胜感激。

1
我不是Java专家,但如果您控制应用程序/线程向下发送流,那么您不能发送一个消息结束字节/几个字节,以便读取器可以使用它来确定流应该关闭吗? - Smudge202
6个回答

4

只有在有数据可用时才调用read()

可以这样做:

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

flagBlock设置为停止读取。


4
available() 方法只会告诉你在不进行系统调用的情况下是否有可用数据。当有数据可读时,它可能返回0。请注意,此方法仅提供数据是否可用的信息,不会触发实际的读操作。 - Peter Lawrey
1
好的,这正是我之前所拥有的。然而它使用了大量的 CPU,因为循环一直在运转。我想使用阻塞 I/O 可以解决这个问题。 - tajmahal
3
你可以在while循环的最后一行加入一个“sleep”语句。这样可以避免忙碌等待。 - Pedro Loureiro
这个解决方案并不是最优雅的,但是在Android上它是我唯一能够正常工作的。 - tajmahal
3
请注意,Android文档中关于available()方法的警告:“请注意,该方法提供的保证非常弱,实际上并不是非常有用。” - Tom

4

请参考《Java并发编程实践》,该书提供了一种非常好的与套接字相关的线程取消方法。它使用了一个特殊的执行器(CancellingExecutor)和一个特殊的可调用任务(SocketUsingTask)。


3
当另一端关闭连接时,您的流将在read()上返回-1。如果您无法触发另一端关闭连接,例如通过关闭输出流,您可以关闭套接字,这将导致阻塞read()线程中的IOException。
您能提供一个简短的示例来重现您的问题吗?
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();

打印

104
105
10
java.net.SocketException: Socket closed

1
这就是我想的。但是我没有收到异常。即使套接字已关闭,read()仍然阻塞。 - tajmahal
4
谢谢你提供这个漂亮的代码示例!编译为普通Java应用程序时,该示例可以正常工作。将其放入一个最简Android应用程序中后发现异常显然没有被抛出,尽管使用的是完全相同的java.net类。因此,我认为问题在于Android上。有什么想法吗? - tajmahal
1
我已经擅自添加了Android标签,因为这似乎是一个关键细节。 ;) - Peter Lawrey

2
我们遇到了同样的问题:在切换网络时(例如,在下载过程中从3G切换到WiFi),没有任何异常抛出。
我们使用的代码来自于http://www.androidsnippets.com/download-an-http-file-to-sdcard-with-progress-notification,除了在某些情况下网络连接丢失时无法正常工作外,其它情况下都能完美运行。
解决方法是指定超时时间,这个值默认为0(表示无限等待)。
    HttpURLConnection c = (HttpURLConnection) u.openConnection();
    c.setRequestMethod("GET");
    c.setDoOutput(true);
    c.setReadTimeout(1000);
    c.connect();

尝试使用适合您的超时值进行实验。


2
我在三星2.3上遇到了这样的问题。当从3G切换到Wifi时,InputStream.read()方法会阻塞。我尝试了这个主题中的所有提示,但没有帮助。从我的角度来看,这是设备特定的问题,因为它应该由于 javadoc 抛出 IOException。我的解决方案是监听android广播 android.net.conn.CONNECTIVITY_CHANGE 并在另一个线程中关闭连接,这将导致阻塞线程抛出IOException。

以下是代码示例:

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

1
你可以使用 java.nio 包。NIO 代表非阻塞 IO。这里的调用(比如读取和写入)不会被阻塞。这样你就可以关闭流。
这里有一个示例程序,你可以在 这里 查看。方法: processRead

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接