6

Google is providing 2 different examples of HttpURLConnection usage.

Calling InputStream's close

http://developer.android.com/training/basics/network-ops/connecting.html

// Given a URL, establishes an HttpUrlConnection and retrieves
// the web page content as a InputStream, which it returns as
// a string.
private String downloadUrl(String myurl) throws IOException {
    InputStream is = null;
    // Only display the first 500 characters of the retrieved
    // web page content.
    int len = 500;

    try {
        URL url = new URL(myurl);
        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
        conn.setReadTimeout(10000 /* milliseconds */);
        conn.setConnectTimeout(15000 /* milliseconds */);
        conn.setRequestMethod("GET");
        conn.setDoInput(true);
        // Starts the query
        conn.connect();
        int response = conn.getResponseCode();
        Log.d(DEBUG_TAG, "The response is: " + response);
        is = conn.getInputStream();

        // Convert the InputStream into a string
        String contentAsString = readIt(is, len);
        return contentAsString;

    // Makes sure that the InputStream is closed after the app is
    // finished using it.
    } finally {
        if (is != null) {
            is.close();
        } 
    }
}

Calling HttpURLConnection's disconnect

http://developer.android.com/reference/java/net/HttpURLConnection.html

   URL url = new URL("http://www.android.com/");
   HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
   try {
     InputStream in = new BufferedInputStream(urlConnection.getInputStream());
     readStream(in);
    finally {
     urlConnection.disconnect();
   }
 }

For resource leakage and performance consideration (Need not to setup network connection from ground up, as my app will communicate with same server most of the time), should we

  1. Call HttpURLConnection's disconnect only.
  2. Call the InputStream's close only.
  3. Call both HttpURLConnection's disconnect & InputStream's close (Haven't seen such official example so far).
Cheok Yan Cheng
  • 47,586
  • 132
  • 466
  • 875

2 Answers2

7

According to Oracle's Java, calling the disconnect() may close the underlying socket connection, if the connection is idle (not fetching anything). This "indicates that other requests to the server are unlikely in the near future" using that same HttpUrlConnection instance. You will have to reopen a new socket connection by creating another HttpUrlConnection.

However, Google has modified HttpUrlConnection so that socket connections can be reused. In Android, the underlying Socket used by a HttpUrlConnection may be persisted and reused for multiple requests. If you call disconnect after completing your request, it may send the socket to a pool containing other idle connections, ready to be reused. The system does this to reduce latency.

From the documentation:

Unlike other Java implementations, this will not necessarily close socket connections that can be reused. You can disable all connection reuse by setting the http.keepAlive system property to false before issuing any HTTP requests.

So you should call disconnect to release resources (streams and sockets), but some of the resources released may be reused (i.e. the socket will go into a pool of idle sockets, ready for the next HttpUrlConnection instance to reuse it).

As for why the first example did not call disconnect(), that is the Java SE way of reusing the connection (I think, it's been a while). What the author did was manually close the InputStream of the connection and left the socket open and idle for reuse. The second example shows the correct way on Android.

In summary: calling disconnect() on Android will close any InputStream or OutputStream used for the connection and may send the socket used for the connection to a pool, ready to be reused for other requests. So, if you call disconnect(), there is no need to call InputStream#close().

ugo
  • 2,705
  • 2
  • 30
  • 34
  • Is there any test/experiment I can conduct to confirm on calling `disconnect` is the right way? – Cheok Yan Cheng Apr 17 '14 at 17:13
  • @CheokYanCheng It depends on what you want to test for. If you want to test that `disconnect()` does release resources, take a look at [Android test sources for UrlConnection](http://goo.gl/w7K7LK). In particular, check the end of the test methods [`test_getContent()`](http://goo.gl/K3s2dN) and [`test_getContentStream()`](http://goo.gl/f9TdKE). These show that the streams are indeed expected to be closed after a call to `disconnect()`. – ugo Apr 17 '14 at 17:53
3

Both close and disconnect method release the connection if not yet released with following 2 differences:

  1. close method throws IOException. Hence in first example, the enclosing method downloadUrl's signature has throws IOException. Where as disconnect method does not throw any exception.
  2. Calling close method ensures that any future references made to the closed connection such as read operation, will result in IOException.

The most important design fact about Android's HttpURLConnection implementation is :

Connection is held when the last byte of the response is consumed. When the response if fully read, the connection is released and will be pooled immediatetly.

You can see in the below image, variable connection & connectionReleased are set to null and true respectively, as soon as all data is read. In this case calling disconnect makes no difference, calling close just ensures future calls on closed connection throws IOException.

enter image description here

If data is still available on the InputStream, close or disconnect has to be called to release the connection explicitly. In this case the connection is not reused and underlying socket connection is also closed. This is done in anticipation that more data may arrive on the InputStream.

You can see in below code snippets, both disconnect and close will finally invoke httpEngine.release(false), which will close the connection without adding to the connection pool.

disconnect implementation:

@Override public final void disconnect() {
        // Calling disconnect() before a connection exists should have no effect.
        if (httpEngine != null) {
            httpEngine.release(false);
        }
    }

close implementation:

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

unexpectedEndOfInput implementation:

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

release implementation:

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

Summary :

  • Connection is automatically released to the connection pool as soon as the last byte of the response is consumed.
  • If the IOException will be handled by method operating on InputStream, use disconnect. If the IOException will be handled by the caller of the method operating on InputStream, use close. Remember close ensures IOException is thrown when read operation is performed on closed connection in future.
Manish Mulimani
  • 17,535
  • 2
  • 41
  • 60