android - 当抛出 IOException 时,我们是否需要使用 HttpURLConnection 的错误流

标签 android

根据Oracle Java的技术指南,当IOException抛出时,我们应该使用HttpURLConnection的错误流

http://docs.oracle.com/javase/6/docs/technotes/guides/net/http-keepalive.html

What can you do to help with Keep-Alive? Do not abandon a connection by ignoring the response body. Doing so may results in idle TCP connections. That needs to be garbage collected when they are no longer referenced.

If getInputStream() successfully returns, read the entire response body.

When calling getInputStream() from HttpURLConnection, if an IOException occurs, catch the exception and call getErrorStream() to get the response body (if there is any).

Reading the response body cleans up the connection even if you are not interested in the response content itself. But if the response body is long and you are not interested in the rest of it after seeing the beginning, you can close the InputStream. But you need to be aware that more data could be on its way. Thus the connection may not be cleared for reuse.

Here's a code example that complies to the above recommendation:

这是代码示例

try {
        URL a = new URL(args[0]);
        URLConnection urlc = a.openConnection();
        is = conn.getInputStream();
        int ret = 0;
        while ((ret = is.read(buf)) > 0) {
          processBuf(buf);
        }
        // close the inputstream
        is.close();
} catch (IOException e) {
        try {
                respCode = ((HttpURLConnection)conn).getResponseCode();
                es = ((HttpURLConnection)conn).getErrorStream();
                int ret = 0;
                // read the response body
                while ((ret = es.read(buf)) > 0) {
                        processBuf(buf);
                }
                // close the errorstream
                es.close();
        } catch(IOException ex) {
                // deal with the exception
        }
}

这是否适用于 Android 平台?因为我在大多数 Android 代码示例中都没有看到这种技术。

最佳答案

如果您不想向用户显示错误消息,请关闭 InputStream 或在 中的 HttpURLConnection 上调用 disconnect finally 阻塞而不阅读错误信息。这就是您在大多数示例中看到的内容。

我在一个 source code 中看到了以下评论,同时浏览 HttpURLConnection 的实现。这可能是连接在未读取所有数据的情况下关闭的原因。

This should be invoked when the connection is closed unexpectedly to invalidate the cache entry and to prevent the HTTP connection from being reused. HTTP messages are sent in serial so whenever a message cannot be read to completion, subsequent messages cannot be read either and the connection must be discarded.

根据Android对HttpURLConnection的实现,如果出现异常:

  • 如果未读取到错误且InputStream 已关闭,则连接将被视为不可重用并关闭。
  • 如果您读取错误然后关闭 InputStream,连接将被视为可重用并添加到连接池中。

您可以在下图中看到,变量 connectionconnectionReleased 分别设置为 nulltrue ,一旦所有数据被读取。请注意,getErrorStream 返回的是 InputStream,因此它在异常情况下也有效。

enter image description here

代码分析:让我们看看FixedLengthInputStream专门的 InputStream 实现之一。下面是 close 方法的实现:

 @Override public void close() throws IOException {
    if (closed) {
        return;
    }
    closed = true;
    if (bytesRemaining != 0) {
        unexpectedEndOfInput();
    }
 }

实例变量 bytesRemaining 包含 InputStream 上仍然可用的字节数以供读取。这是 unexpectedEndOfInput方法实现:

protected final void unexpectedEndOfInput() {
    if (cacheRequest != null) {
        cacheRequest.abort();
    }
    httpEngine.release(false);
}

这是 release方法实现。在 HttpURLConnection 实例上调用 disconnect 会导致调用以 false 作为参数的此 release 方法。 最后的 if 检查确保连接是否需要关闭或添加到连接池以供重用。

public final void release(boolean reusable) {
    // If the response body comes from the cache, close it.
    if (responseBodyIn == cachedResponseBody) {
        IoUtils.closeQuietly(responseBodyIn);
    }
    if (!connectionReleased && connection != null) {
        connectionReleased = true;
        // We cannot reuse sockets that have incomplete output.
        if (requestBodyOut != null && !requestBodyOut.closed) {
            reusable = false;
        }
        // If the headers specify that the connection shouldn't be reused, don't reuse it.
        if (hasConnectionCloseHeader()) {
            reusable = false;
        }
        if (responseBodyIn instanceof UnknownLengthHttpInputStream) {
            reusable = false;
        }
        if (reusable && responseBodyIn != null) {
            // We must discard the response body before the connection can be reused.
            try {
                Streams.skipAll(responseBodyIn);
            } catch (IOException e) {
                reusable = false;
            }
        }
        if (!reusable) {
            connection.closeSocketAndStreams();
            connection = null;
        } else if (automaticallyReleaseConnectionToPool) {
            HttpConnectionPool.INSTANCE.recycle(connection);
            connection = null;
        }
    }
}

您共享的代码,其中处理 IOException,读取错误流然后关闭,确保 Connection 可重用并添加到连接池中。从 InputStream 读取所有数据的那一刻,Connection 被添加到连接池中。下面是 FixedLengthInputStreamread 方法实现:

@Override public int read(byte[] buffer, int offset, int count) throws IOException {
        Arrays.checkOffsetAndCount(buffer.length, offset, count);
        checkNotClosed();
        if (bytesRemaining == 0) {
            return -1;
        }
        int read = in.read(buffer, offset, Math.min(count, bytesRemaining));
        if (read == -1) {
            unexpectedEndOfInput(); // the server didn't supply the promised content length
            throw new IOException("unexpected end of stream");
        }
        bytesRemaining -= read;
        cacheWrite(buffer, offset, read);
        if (bytesRemaining == 0) {
            endOfInput(true);
        }
        return read;
    }

bytesRemaining变量变为0时,endOfInput被调用,它将进一步调用带有 true 参数的 release 方法,这将确保连接被合并。

protected final void endOfInput(boolean reuseSocket) throws IOException {
        if (cacheRequest != null) {
            cacheBody.close();
        }
        httpEngine.release(reuseSocket);
    }

关于android - 当抛出 IOException 时,我们是否需要使用 HttpURLConnection 的错误流,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/23136475/

相关文章:

android - 在cordova webview中访问设备 native 功能

Android Instrumented 测试配置问题

java - Android 中类之间的全局变量重置为 0

android - 如何使用 styles.xml 自定义 SpinnerItemStyle

android - Nativescript获得调试以在VS Code中工作

安卓 : Showing keyboard hides listview contents

java - wp :featuredmedia of type org. json.JSONArray 无法转换为 JSONObject

java - 在viewpager View 中保存按钮值

android - Android Google Map 中的高效 map 叠加层

android - 声明 Activity 的目的是什么?