Android の java.net.HttpURLConnection

Android SDKで使えるHTTPクライアントクラスについて、gitから取ってきたソースを見つつその挙動を調べてみる。賞味期限の短い記事だと思うので、利用には注意してほしい。

Android SDKで使えるHTTPクライアントクラスは三つある。

  • java.net.HttpURLConnection
  • org.apache.http.impl.client.DefaultHttpClient
  • android.net.http.AndroidHttpClient

android.net.http は API Level 8 からで、当面の間は使うことができないだろう。
残り2つのうち、今回は古き良き java.net.HttpURLConnection について調べてみた。

java.net.HttpURLConnection

Android OS 2.1 では、実際に使われるクラスはorg.apache.harmony.luni.internal.net.www.protocol.http.HttpURLConnectionImpl だ。ソースコードhttp://android.git.kernel.org/ の platform/libcore/luni/src/main/java/org/apache/harmony/luni/internal/net/www/protocol/http
の中にある。

ソースのそこかしこに

    // BEGIN android-added
    // END android-added

のような記述が見られる。android 固有の変更が色々と入っているようだ。

名前解決

名前解決が行われるのはconnect()が呼ばれた時だ。
HttpURLConnectionImpl はそこで
HttpConnectionManager.getDefault().getConnection() に接続先の情報を渡す。
HttpConnectionのコンストラクタは java.net.InetSocketAddress のインスタンスを作成して、InetSocketAddress はそのコンストラクタで InetAddress.getByName() を呼びだす。InetAddress.getByName()は InetAddress 中の次のネイティブメソッドを呼び出す。

private static native byte[][] getaddrinfo(String name) 
throws UnknownHostException;

この関数のソースは platform/libcore/luni/src/main/native/java_net_InetAddress.cpp にあり、その中では sys/socket.h の getaddrinfo が呼び出される。この呼び出しの前後では getaddrinfo の返すエラーコードはあまり真面目に処理されておらず、UnknownHostException と SecurityException 以外は帰ってこない。よくわからないエラーは全て UnknownHostException として扱われる。 Thread.interrupt() で中断させることができたのかどうかは、確実には分からないようだ。

connect()とdoRequest()

getResponseCode() や getInputStream()を読ぶと、自動的に内部で connect() と doRequest() が呼ばれる。ただし、明示的に connect() と getResponseCode() を呼ぶようにした方が動作が安定するようだ。特に connectでエラーになるケースは多いので注意したい。

getInputStream()

レスポンスを正しく読めた場合は getInputStream().close() を呼ぶと場合によっては HttpConnectionManager.returnConnectionToPool() が呼ばれて、リソースの浪費を回避できる。
connectに失敗した場合も含めて、エラーが起きた場合は常にdisconnect()を呼び出すべきだろう。
ちなみに closeした後にdisconnectを呼んでも、既にconnectionへの参照をもっていないので何も起こらない。

キャンセル手順

他のスレッドからHttpURLConnectionのブロックをキャンセルしたい場合、Thread.interrupt()でブロックをキャンセルできるようだ。ただし一回の呼び出しですむかどうかは分からないし、一定時間内に戻ってくる保証もないし、InterruptedExceptionが発生しない場合もある。
connectやsocketのreadには事前にtimeoutを設定しておくべきだろう。また名前解決にはタイムアウトを設定できないため、長時間待たされる可能性もある。