読者です 読者をやめる 読者になる 読者になる

tumblr

tumblr(タンブラー)は、メディアミックスブログサービス。ブログとミニブログ、そしてソーシャルブックマークを統合したマイクロブログサービスである。アメリカのDavidville.inc(現: Tumblr, Inc.)により2007年3月1日にサービスが開始された。

OkHttpを使う

androidのhttpクライアントはデフォルトのを使う人はあんまり居ないんじゃないかと思う。 androidのhttpクライアントというとvolleyかokhttpの二択という感じがあるが、最近ではOkHttpのほうが動きが活発なのでそちらを選択する人が多いように見えるのでこっちに乗っかってみる。

インストール

android studioを使っているのなら、build.gradleに以下を突っ込む。

compile 'com.squareup.okhttp:okhttp:2.7.0'

かProjectStructureからLibraryDependencyで「okhttp」と検索してcom.squareupのものを追加する。

基本的な使い方

OkHttpClient client = new OkHttpClient();

public String run(String url) throws IOException {
  Request request = new Request.Builder()
      .url(url)
      .build();

  Response response = client.newCall(request).execute();
  return response.body().string();
}

同期的な通信の手順としては

  1. OkHttpClientインスタンスの作成
  2. Requestインスタンスの作成
  3. execute()で実行

という感じ。

上のコードはOkHttpの公式サイトから引っ張ってきたものだけど、多分これと同じで実際に使うときはラッパーを用意してそいつに通信先のURLとか渡すことになると思う。

コールバックを使う

といっても同期通信よりもコールバックを用いた非同期通信をすることのほうが多いだろう。コールバックを用いた非同期通信は以下のようにする。

private final OkHttpClient client = new OkHttpClient();

  public void run() throws Exception {
    Request request = new Request.Builder()
        .url("http://publicobject.com/helloworld.txt")
        .build();

    client.newCall(request).enqueue(new Callback() {
      @Override public void onFailure(Request request, IOException e) {
        e.printStackTrace();
      }

      @Override public void onResponse(Response response) throws IOException {
        if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);

        Headers responseHeaders = response.headers();
        for (int i = 0, size = responseHeaders.size(); i < size; i++) {
          System.out.println(responseHeaders.name(i) + ": " + responseHeaders.value(i));
        }

        System.out.println(response.body().string());
      }
    });
  }
  1. OkHttpClientインスタンスの作成
  2. Requestインスタンスの作成
  3. コールバックオブジェクトの作成
  4. enqueueにコールバックを入れて通信開始
  5. 通信終了後にコールバック(onFailure または onResponse)呼び出し

上のコードではコールバックをそのままenqueueに突っ込んでるので3と4が同時だけど、当然コールバックを先に作ってからenqueueに渡すということもできる。なのでこれも

public void runAsync(String url, Callback callback)

みたいなラッパーを作って使うことが多くなるんじゃないかと思う。

onFailureとonResponse

コールバックの直接の処理となるonFailureとonResponseだが、400系や500系のレスポンスが返ってきた時はコールバックとしてonFailureが呼ばれるんじゃないかと思っていたのだけど、じつは違う。 onFailureはサーバ側までそもそもリクエストが届かなかった時などに呼び出される。 なので400系や500系も一応通信相手のサーバまで届いたものの「ダメでした」というレスポンスをもらったとしてカウントされるため、それらの場合も全部onResponseで処理することになる。 なのでonResponseのなかでresponse.code()でレスポンスコードを見て色々処理を出し分けたり、200系とそうでないものの区別が付けばいいだけなら上のコードのようにresponse.isSuccessful()でざっくり判断すればいい。

POSTリクエスト

もちろんPOSTも出来る。

public static final MediaType MEDIA_TYPE_MARKDOWN
      = MediaType.parse("text/x-markdown; charset=utf-8");

  private final OkHttpClient client = new OkHttpClient();

  public void run() throws Exception {
    String postBody = ""
        + "Releases\n"
        + "--------\n"
        + "\n"
        + " * _1.0_ May 6, 2013\n"
        + " * _1.1_ June 15, 2013\n"
        + " * _1.2_ August 11, 2013\n";

    Request request = new Request.Builder()
        .url("https://api.github.com/markdown/raw")
        .post(RequestBody.create(MEDIA_TYPE_MARKDOWN, postBody))
        .build();

    Response response = client.newCall(request).execute();
    if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);

    System.out.println(response.body().string());
  }

GETの時とほとんど同じ。Requestインスタンスを作成する際にpost()メソッドにリクエストボディを指定してあげればいい。リクエストボディはリクエストボディそのものとメディアタイプを指定することで作成。

POSTでフォームとか画像送ったりヘッダー指定したりしたいんだけど

基本は公式のgithub内にあるレシピ集を見ればだいたいなにが出来るか、どうやるかはわかると思う。

github.com

リクエストの前後にいろいろな処理を噛ませたい

例えばリクエストの際には基本的にあるヘッダーを付けたいとかって場合、

Request request = new Request.Builder()
        .url("https://api.github.com/repos/square/okhttp/issues")
        .header("User-Agent", "OkHttp Headers.java")
        .addHeader("Accept", "application/json; q=0.5")
        .addHeader("Accept", "application/vnd.github.v3+json")
        .build();

というようにしてヘッダーをつけることもできるけど、OkHttpClientインスタンスにその役割を任せることも出来る。interceptorという機能を使う。

class LoggingInterceptor implements Interceptor {
  @Override public Response intercept(Chain chain) throws IOException {
    Request request = chain.request();

    long t1 = System.nanoTime();
    logger.info(String.format("Sending request %s on %s%n%s",
        request.url(), chain.connection(), request.headers()));

    Response response = chain.proceed(request);

    long t2 = System.nanoTime();
    logger.info(String.format("Received response for %s in %.1fms%n%s",
        response.request().url(), (t2 - t1) / 1e6d, response.headers()));

    return response;
  }
}

OkHttpClient client = new OkHttpClient();
client.networkInterceptors().add(new LoggingInterceptor());

という感じでInterceptorインターフェースの実装クラスを作ってOkHttpClientインスタンスにそのインスタンスを持たせることで処理を噛ませることができる。この場合はリクエストした時刻とレスポンスの返ってきた時刻をログに書き出すということをしている。 chain.request()で取得しているのがリクエスト時に作ったRequestインスタンス、chain.proceed(request)で取得しているのがコールバックのonFailureやonResponseに渡る直前のResponseインスタンス

NetworkInterceptorとApplicationInterceptor

interceptorにも2種類ある。それがNetworkInterceptorとApplicationInterceptor。

OkHttpClient client = new OkHttpClient();

// NetworkInterceptor
client.networkInterceptors().add(new LoggingInterceptor());

// ApplicationInterceptor
client.interceptors().add(new LoggingInterceptor());

指定の仕方はほとんど違わない。何が違うのかというと処理の回数が違う。 ApplicationInterceptorは1回のリクエストあたり1度きりしか処理をしない。NetworkInterceptorは1回のリクエストあたり複数回処理をすることがある。 というのは例えばリクエスト先Aが300系を返してBにリダイレクトを促した場合、リダイレクト先Bにもう1度リクエストを行うことになる。 なのでこの場合は2回リクエストが行われているのだけど、ApplicationInterceptorの場合はその場合でも1度しか呼ばれない。NetworkInterceptorの場合は2度呼ばれることになる。

なのでローレベルで色々処理を噛ませたいのならばNetworkInterceptorを、そうでないのならばApplicationInteceptorを使うことになる。ApplicationInterceptorがあれば十分な場合のが多いのではないだろうか。

というようなことが以下に丸々書いてある。

github.com

ちなみに

OkHttpClient client = new OkHttpClient();
client.interceptors();

などinterceptors()メソッドやnetworkInterceptors()で取得できるのはList。なので、addができるならremoveもできる。つまりInterceptorの取り外しが可能。ログイン中は処理を噛ませたいけど、ログインしてない時はこの処理を噛ませたくない、というような場合なら、addした時のInterceptor実装クラスインスタンスを保存しておいて任意のタイミングでadd,removeができるので便利。

今はOkHttpをそのまま使うことはなくRestAPIインターフェースライブラリのRetrofitのhttpクライアントとして使っている。 なのでexecute()とかをそのままいじることは殆ど無いけど、InterceptorまわりはRetorfitを使っていてもいじることは結構あるのでinterceptor周りは覚えておいて損はないと思う。