HttpClientまわりのクラスについてメモ。
AndroidでWebサーバにGETリクエストやPOSTリクエストを投げてデータを受け取る処理の勉強をしていて、メソッドの書き方などは読んだのだけど、
HttpClientまわり、どういうメソッドやクラスが登場しているかがいまいちすっきりしなかったので少し調べてメモしてみた。
かなりざっくりしているので、詳しいことは詳しいところへ行って読んでください。
どうやって書いたらいいのってコードを見たい人はこちら。
http://terurou.hateblo.jp/entry/20110702/1309541200
とりあえず、以下は単純にGETリクエストを投げる場合を主に書きます。
HttpClientまわりの登場人物
- Uri.Builder
- HttpGet/HttpPost
- HttpClient/DefaultHttpClient
- ResponseHandler
- EntityUtils
- ConnectionManager
Uri.Builder
見たままですが、HTTPリクエストの宛先のURIを作る。
scheme(),authority()などのメソッドを持っています、とか見ていても少し分かりにくかったので、実際メソッドによってどんな感じにURIが出来るのか、http://www.serendip.ws/archives/5107から引用します。
コード
Uri.Builder builder = new Uri.Builder();
builder.scheme("http");
builder.authority("www.serendip.ws");
builder.path("/hoge/foo");
builder.appendQueryParameter("key1", "val1");
builder.appendQueryParameter("key2", "Encodeされたテキスト");
builder.fragment("fragment(フラグメント)");
TextView tv = (TextView) findViewById(R.id.result);
tv.setText(builder.build().toString());
出力結果
http://www.serendip.ws/hoge/foo?key1=val&key2=Encode%E3%E81...("Encodeされたテキスト"がエンコーディングされたもの)#fragment((%E3%83…("フラグメント"がエンコーディングされたもの))
appendQueryParameter()がGETリクエストのURIを作っていくときに便利です。
fragmentメソッドがURIフラグメント(リソース内の部分指定をする)を追加すると書いてあったのがよくわからなかったのですが、URIの#の後ろの部分をフラグメントというらしい。
たとえば、
Yukiの枝折: Android:UriとUri.BuilderのAPI
で説明されている例で一番簡単なものだと、
http://autority/path#a=aのa=aの部分。
結果の#以降の部分
#fragment(%E3%83…(フラグメントを(何に)変換してる))
とbuilder.fragment("fragment(フラグメント)”);
の部分がこれにあたってるみたい。
HttpGet/HttpPost
このへんはさっきURIビルダーで作ったURIとHTTPメソッドを
HttpGet httpGet = new HttpGet(Uri);
という具合にむすびつけたリクエストを扱う感じ。
URIは直接
HttpGet httpGet = new HttpGet("http://express.heartrails.com/api/json?method=getAreas”);
という感じに直接入れてもいい。
(ちなみにこのリクエストを投げているAPIはこちらhttp://express.heartrails.com/のもの。)
HttpPostの場合はUri.Builderでパラメータを追加するわけにはいかないので、
HttpPost httpPost = new HttpPost(URI);
とリクエストを投げたいURIを持ったHttpPostオブジェクトをひとまず作る(後述のEntityを作った後でも前でもどっちでもいい)。
そのあとで、
送りたいデータをEntityの中に詰めて、setEntityメソッドでHttpPostオブジェクトにセットする。
参考にしたhttp://319ring.net/blog/archives/1667からの引用
(書いてる人は頭がよろしくないのでさらにコメントを足してある)
ArrayList params = new ArrayList();
params.add(new BasicNameValuePair("subject", "件名"));
params.add(new BasicNameValuePair("body", "本文です。こんにちは!"));
// HttpPostインスタンスを生成した後、
HttpPost httpPost = new HttpPost("http://www.example.com/ “);
// HttpPostインスタンスにsetEntityメソッドを使って先ほどの配列をセット
// (ただしエンコード変換用のメソッドUrlEncodedFormEntityを使うこと)
// パラメータを設定
httpPost.setEntity(new UrlEncodedFormEntity(params, "UTF-8"));
HttpClient/DefaultHttpClient
HttpClientは上までのクラスやメソッドで作ったHttpリクエストを送る役。
実際にデータの送受信を行うので、通信関係やI/O関係の例外をキャッチする必要があって、
try { … } catch () { … } の構文の中にいる。
具体的には
//httpClientはHttpClientクラスのインスタンスとする
httpClient.execute(HttpGetクラスなどのインスタンス(=HTTPリクエストの情報),ResponseHandler);
という感じで,投げたいHttpリクエストとリクエストに対するレスポンスが返って来たときの処理
を書いた関数をexecute()の中に入れて使う。
DefaultHttpClientはHttpClientのサブクラスで、
HttpClientのインスタンスはほとんどexecute()くらいしか出来ないが、
DefaultHttpClientの場合、createCookieStore()など、
HTTPで通信を使うときにはこれするよねーってメソッドが追加されているので、
特に理由がない場合はDefaultHttpClientのほうがよさそう。
そういえば、HttpURLConnection and Apache HTTP Client
だとApacheのほうが古いOS(Android 2.2以前)でいくつかバグが有るらしいと
AndroidDeveloperサイトあたりに書いてあったのだけど、
2.3以降でHttpURLConnectionのほうを使えばいいのでしょうか。
ResponseHandler
HttpClient/DefaultHttpClientのexecute()メソッドで送ったHTTPリクエストに対する結果が帰って来た場合の処理を書く。
このメソッドの返り値がHttpClient.execute()の戻り値になる。
ResponseHandler() {
@Override
public String handleResponse(HttpResponse response)
throws ClientProtocolException, IOException {
return EntityUtils.toString(response.getEntity());
}
}
HttpResponse responseはHTTPリクエストを出した結果サーバから返って来ているデータ+通信情報の固まりみたいなゆるふわな理解をしている。それで、その中には、目当てのデータのかたまりであるEntityの他に通信のステータスコード(404とか)など、通信状態などの情報が入っている(たぶん)。
なので、getEntity()メソッドでデータの固まりのEntity()だけを取り出している。
Entity()の形だと処理しにくいから、次にメモするEntityUtilsクラスのメソッドで加工してから返す。
あと、ResponseHandlerの<>の中身(ジェネリックの型)とhandleResponseの返り値の型は一致する必要が有る(いちおう)。
EntityUtils
受け取ったレスポンスから取り出したEntityをなじみのある形(String型など)に変換するときに使うクラス。
インスタンスを生成した覚えがないのに一体お前は何なんだと思ったら
Static helpers gor dealing with entities.と書いてありました。申し訳ありません。
return EntityUtils.toString(response.getEntity(),”UTF-8(文字コード)”);
のようにレスポンスから取り出したEntityと必要なら文字コードなど変換形式に関する情報を引数に入れて使う。
変換するオブジェクトのクラスや形式はいくつか選べるけれど、とりあえず、自分の場合はJSON文字列を受け取りたかったので、これでよし。
後は受け取った側のメソッドでJSONオブジェクトに直したりする。
ConnectionManager
通信の処理なので、通信が終わったらshutdown()しないとリークが起きますよ、という話。
try {
//HttpClient.execute()などを記述
} catch (…) {
} finally {
// 最終的に処理が流れ着く場所
// このへんでhttpClient.getConnectionManager().shutdown();
// をしておかないと、メモリリークが起きる。
}
他にやることがたくさんあった気がするけど、とりあえず忘れる前にまとめておいた記事をアップしてすっきりしておく。