如何在 Android 上解除对 InputStream.read() 的阻止?

Posted

技术标签:

【中文标题】如何在 Android 上解除对 InputStream.read() 的阻止?【英文标题】:How to unblock InputStream.read() on Android? 【发布时间】:2011-09-28 14:27:37 【问题描述】:

我有一个线程,其中InputStreamread() 方法在循环中被调用。当没有更多字节要读取时,流将阻塞,直到有新数据到达。

如果我从另一个线程调用InputStream 上的close(),流将关闭,但阻塞的read() 调用仍然被阻塞。我会假设read() 方法现在应该返回值-1 以指示流的结束,但事实并非如此。相反,它会再阻塞几分钟,直到发生 tcp 超时。

如何解除对close() 呼叫的阻止?

编辑:

显然,当阻塞read()调用对应的流或套接字是close()'d时,常规JRE会立即抛出SocketException。但是,我使用的 android Java 运行时不会。

任何有关 Android 环境解决方案的提示将不胜感激。

【问题讨论】:

我不是 java 人,但如果你控制应用程序/线程向下发送流,你不能发送一个读者可以使用的消息结束字节/几个字节确定应该关闭流? 【参考方案1】:

您可以使用java.nio 包。 NIO 代表非阻塞 IO。这里的调用(比如说读和写)没有被阻止。这样你就可以关闭流了。

您可以查看here 的示例程序。方法:processRead

【讨论】:

【参考方案2】:

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

做这样的事情:

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

设置flagBlock 停止读取。

【讨论】:

available() 只会在不进行系统调用的情况下告诉您数据是否可用。当有数据要读取时,这可以返回 0。 好的。这正是我以前所拥有的。但是,它使用了大量的 cpu,因为循环一直在旋转。我认为使用阻塞 I/O 可以解决这个问题。 您可以添加 sleep 作为您的 while 循环的最后一行。这将防止您忙于等待 这个解决方案不是最优雅的解决方案,但却是我唯一能在 Android 上正常工作的解决方案。 请注意,available() 的 Android 文档以警告开头“请注意,此方法提供的保证很弱,因此在实践中不是很有用。”【参考方案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

【讨论】:

我就是这么想的。但我没有例外。即使套接字关闭,read() 也会继续阻塞。 感谢您提供漂亮的代码示例!该示例在将其编译为常规 Java 应用程序时运行良好。将它放到一个简单的 Android 应用程序中会发现异常显然没有被抛出,即使使用了相同的 java.net 类。所以我认为问题在于Android。有什么想法吗? 我冒昧地将 Android 添加为标签,因为这似乎是一个关键细节。 ;)【参考方案4】:

请参阅Java Concurrency In Practice,了解在使用套接字时取消线程的非常好的系统。它使用一个特殊的 executor (CancellingExecutor) 和一个特殊的 Callable (SocketUsingTask)。

【讨论】:

【参考方案5】:

我们遇到了同样的问题:切换网络时也不例外(例如,下载时从 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();

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

【讨论】:

【参考方案6】:

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

这里是代码示例:

下载Thread.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;

【讨论】:

以上是关于如何在 Android 上解除对 InputStream.read() 的阻止?的主要内容,如果未能解决你的问题,请参考以下文章

linux解除绑定ssh源地址

输出流FileWriter和FileInputStream,和一个可以处理中文的OutputStreamWriter,如果要对中文进行传输和写入用OutputStreamWriter和InputStr

Android蓝牙开发——经典蓝牙:配对与解除配对 & 实现配对或连接时不弹出配对框

如何在 linux C 中解除对 recv() 或 recvfrm() 函数的阻塞

华为的朗读模式怎么解除?

防止在Android中的屏幕旋转对话框解除