2013年5月21日火曜日

Google I/O 2013 - Android : Volley: Easy, Fast Networking for Android

Volley: Easy, Fast Networking for Android
(リクエストで埋め込み無効になってるのでリンクで)

Volley というライブラリについて
  • Android のネットワーク通信処理をより簡単に、速くする
  • Volley と名付けたイメージ : a burst or emission of many things or a large amount at once

ネットワーク処理で必要なこと
  • JSON, image, raw text の処理
  • メモリキャッシュとディスクキャッシュ
  • カスタマイズ能力(ネットワークの優先順位の処理、リトライ時のバックオフアルゴリズムなど)
  • デバッグとトレーシングのツール


But why?(Android には既に HTTP client のサポートがある、Apache HTTP Client, HTTP URL connection)

FourSquare, Twitter, Youtube, ニュース・天気 などでよくある ListView にタイトル、詳細、画像を表示する処理について
  • これらのアプリではレスポンスをキャッシュしたり、リトライしたり、リクエストを並列におこなったりいろんな処理を自前でやっている
  • Volley では wire protocols や network transports のロジカルな操作など、ここ数年 Google で製品クオリティのアプリを開発するなかで学んだことをまとめてインタフェースとして提供している


Volley の使いどころ
  • UI に表示される RPC-style のネットワーク操作(メタデータをフェッチしたり、画像のプロファイルを取得してビューに表示する)に適している
  • バックグラウンドの RPCs でもよい
  • large payloads (ストリーミング処理や大きいファイルのダウンロードとか)には向いていない(レスポンス全体がメモリに届けられるため)
Google では RPCs には Volley をつかい、Volley に適さない部分(long-lived large file download operations など)は Download Manager など他のシステムを使っている



ListView にタイトル、詳細、画像を表示するサンプルを例に紹介
(このサンプルは僕のマイクロブロギングスタートアップのものだから、もしここにエンジェル投資家がいたらあとで会ってね、と言って会場が笑ってた)
  • サーバーから JSON を受けとる
  • JSON にはタイトル、詳細、画像のリンクが入っている

  • リストの最後まで行ったときに次のページを読み込む処理
    • ListAdapter の getView() で最後の位置だったら次のページの読み込み処理を開始
    • AsyncTask を使ってバックグラウンドで HttpURLConnection で通信してレスポンスを JSONObject として取得
    • 「1スライドに納めるためにいろんな処理を省いているよ。AsyncTask の他の部分や try catch (I/O Exception, JSON Exception)、input stream のクローズなど、実際はこれの2倍以上のコードになるよ」
    • AsyncTask の onPostExecute() でリストにデータを追加

  • 画像を非同期で読み込む処理
    • ListAdapter の getView() で画像を読み込む処理を開始
    • AsyncTask を使ってバックグラウンドで HttpURLConnection で通信してレスポンスを Bitmap として取得
    • AsyncTask の onPostExecute() が ImageView に画像をセット


ここまでのサンプル問題点について

■ 全てのネットワーク処理は順番に処理される(AsyncTask の execute を呼んでいるから)
  • ユーザーがページをクロールダウンした場合、以前の画像の処理が終わらないと次のページのデータがとれない(first in, first out でスケジュールされているから)
Volley では
  • 自動でネットワークリクエストをスレッドプール上にスケジュールする
  • スレッドプールのサイズを指定することができるし、デフォルトの4を使うのもOK(Play Store のテストで4スレッドが最適だった)
  • リクエストの優先順位付けをサポート(メタデータの優先順位を高くし、画像を低くするなど)


■ 画面回転がおこると、ネットワークから取得した全てをリロードしないといけない
  • この対策を自分でやるのはけっこう大変、自前のキャッシュを用意してハッシュマップや LRU cache に画像をいれないといけない
  • オンメモリのキャッシュはアプリが生きている間だけ有効なので、ユーザーがアプリを再度起動したときにも全てをリロードしないといけない
Volley では
  • レスポンス用の透明なディスクキャッシュ、メモリーキャッシュを提供する(透明の意味は、呼び出しもとがキャッシュの存在について知る必要がないということ。キャッシュにヒットした場合はものすごく速いネットワークからのレスポンスのように見える)
  • アドバンスなツール、特に画像の読み込みに対するもの、を提供する


■ AsyncTask は recycled views で使うには対策が必要
  • スクロール時にリストの画像の読み込み処理を実行しても、その画像を表示する部分はすでにスクロールされて画面にないかもしれない
  • View holder を作って、URL を保持してそれが一致するかみたりする処理をいれるが、読み込んだ結果を捨てることになるので遅いし無駄が多い
  • AsyncTask をトラックしてリサイクルされたときにキャンセルする処理をいれたりするので大変
Volley では
  • 強力なリクエストキャンセル API を提供する(単一のリクエストを簡単にキャンセルでき、リクエストをキャンセルするブロックやスコープを設定できる)
  • toolbox のところで紹介する予定の network ImageView を使うこともできる


■ Froyo 以前の Http URL Connection は buggy、Apache HTTP client の長らくメンテされてないので同じく buggy

Volley では
  • network transport の下を抽象化
  • 標準のセットアップヘルパーメソッドを使うと、Froyo 以前では Apatche スタックを、Gingerbread 以降は Http URL Connection スタックが取得できる
  • Square の new OkHttp library をポートしたいと言うかもしれない、その場合一ヶ所を置き換えるだけでコード全体をリファクタリングしなくて大丈夫



Volley の実装方法 mRequestQueue = Volley.newRequestQueue(context); mImageLoader = new ImageLoader(mRequestQueue, new BitmapLruCache()); リクエストキュー
  • ネットワークにリクエストの割り当てを行うためのインタフェース
  • スタートアップの早い時期に作成して Singleton として使う
mRequestQueue.add(new JsonObjectRequest(Method.GET, url, null, new Listener() { public void onResponse(JSONObject jsonRoot) { mNextPageToken = jsonGet(jsonRoot, "next", null); List items = parseJson(jsonRoot); appendItemsToList(item); notifyDataSetChanged(); } } }
  • リクエストキューにリクエストを追加
  • レスポンスの受信をリスナーとして提供
  • onResponse() は AsyncTask の onPostExecute() のようなもの(なので既存のコードを簡単に置き換えられる)



画像の取得 if(holder.imageRequest != null) { holder.imageRequest.cancel(); } holder.imageRequest = mImageLoader.get(BASE_UR + item.image_url, holder.imageView, R.drawable.loading, R.drawable.error);

NetworkImageView を使う

・<ImageView> の代わりに <com.android.volley.NetworkImageView> を使う mImageView.setImageUrl(BASE_URL + item.image_url, mImageLoader); ビュー階層から detach されるとリクエストは自動でキャンセルされる

Volley の画像取得ではレスポンスのバッチ処理がすごい
ImageLoader や NetworkImageView を使うと、ライブラリは画像のレスポンスを短い時間保持し、いくつかをまとめて単一のパスとしてメインスレッドに流す


カスタムリクエストを簡単に書ける @Override protected Response<T> parseNetworkResponse(NetworkResponse response) { try { String json = new String(response.data, HttpHeaderParser.parseCharset(response,headers)); return Response.success(gson.fronJson(json, clazz), HttpHeaderParser, parseCacheHeaders(response)); } catch(UnsupportedEncodingException e) { return Response.error(new ParseError(e)); } catch(JsonSyntaxException e) { return Response.error(new ParseError(e)); } } https://gist.github.com/ficusk/5474673

GSON は JSON の serialization と deserialization をしてくれるライブラリ、JSON オブジェクトから特定の Java モデルオブジェクトを生成するためにリフレクションを使っている


GSON の実装
loadMoreData() mRequestQueue.add( new GsonRequest<ListResponse>(url, ListResponse.class, null, new istener<ListResponse>() { public void onResponse(ListResponse response) { appendItemsToList(response.item); notifyDataSetChanged(); } } }

Architecture と Semantics
Volley の内部の実装について
  • Volley はスレッドプールの処理を取り扱う
  • キャッシュを割り当てるスレッドが1つあり、NetworkDispatcher のスレッドが1つ以上ある
  • adb shell setprop log.tag.Volley VERBOSE でログがとれる(サンプルアプリで実際に計測したデータで説明している)


メインスレッドについて

以下のようなコードを書いたことがある? @Override public void onPostExecute(Result r) { if (getActivity() == null) { return; } // ... } すでに Activity が死んでいるのに処理が終わるまで続けるのは CPU、電池、もろもろの無駄

Volley ではすべてのレスポンスがメインスレッドに送られる、メインスレッドからキャンセルした場合レスポンスが送られないことを Volley は保証する @Override public void onStop() { for (Request <?> req : mInFlightRequests) { req.cancel(); } ... } すべてをキャンセルするには @Override pubic void onStop() { mRequestQueue.cancelAll(this); ... } 引数はキャンセルするスコープ

キャンセルするリクエストをフィルターするには @Override public void onStop() { mRequestQueue.cancelAll( new RequestFilter() { ...

まとめ

Google+ チームが他のライブラリとベンチマークをとったけど、Volley が一番速かった

How to get started

1. Clone the Volley project

git clone https://android.googlesource.com/platform/frameworks/volley

2. Import the code into your project

3. Volley.newRequestQueue(context)


QA
# 訳がまちがっているかもしれません(あんまり自信ない。。。)


Q. メモリーキャッシュのサイズはどうなりますか?
A. キャッシュのサイズは自分で設定できます。メモリーキャッシュサイズとディスクキャッシュのサイズは別々に選ぶことができます。 全てのブロッキングIOはバックグラウンド行われ、メインスレッドで行われることはありません。しかし、メモリーキャッシュを HTTP スタックを取得する前にメインスレッドでやることは実際重要なことです。なぜなら、もう一度メインの Looper に戻ってくるより、メインスレッドから離れないままでいたいでしょうから。ビットマップがあるかどうかをすぐに知りたい、そこであとでやらないでレイアウトのパスが起こったときに画像のビットマップをセットすると、すこしがくがく(flicker)するようになります。

Q. ディスクキャッシュとネットワークスレッドの対比について教えてください
A. ディスクからのキャッシングは全てキャッシュスレッドで行われます。キャッシュスレッドでのシリアライゼーションがあります。 十分速いです。

Q. キャッシュ TTL はサポートしてますか?
A. ディスクでの Volley のキャッシュ実装は標準の HTTP cache shutters を尊重しています。あなたのサーバーで、これがただちに有効期限切れ(expires)するようにセットしていれば、これを尊重します。つまりキャッシュに書かれません。1日だけ、1年だけとセットすれば有効期限はそれを尊重します。Volley ではメモリキャッシュに TTL はありません。

Q. 大きい画像を扱ったときや、スペックの低いデバイスでアウトオブメモリーで死ぬことがよくあるのですが Volley はどう対処していますか?
A. 確かにわれわれもアウトオブメモリーエラーでたくさん苦労しています。これはハードな問題です。なにかを速くしようとするとたくさんメモリを使うからです。われわれがたどり着いた実際よく動く解決方法がいくつかありました。1つ目は、前に言ったように CPU コアより多くのものを並列にデコードしません。これは実際とてもいい効果があります。その他のこととして、Play Store ではメモリーキャッシュのサイズを画面サイズに応じた関数で指定しています。つまりは「どのようにキャッシュサイズを決めるのか」だと思います。

Q. SDK Manager に入りますか?
A. 私は知りません。十分簡単に clone できますし、これについてのいい答えはありません。

Q. 小さいサイズの画像をたくさん扱う場合、network protocol SPDY に似ていると思うのですが SPDY を Volley に integrate する予定はありますか?
A. 私は SPDY について本当にわくわくしています。我々が Volley をデザインするとき、SPDY は私の頭のなかにありました。今も誰かが Square がリリースした OkHttp library を入れようとしています。Volley の下のスタック用にです。私はまだ SPDY と一緒に OkHttp を試したことがありません。しかし著者の Jesse によると OkHttp のバックに SPDY を差し込むことができ、そうすれば Volley に SPDY を与えることができます。あなたがやることはできます。我々はまだやってませんが、すぐにやろうと思っています。

Q. Activity に遷移する前に API call を作成しておきたい、リクエストオブジェクトをシリアライズすることはできますか?
A. 先に終えておかなければならない本当にめんどうなことは、1つのワーカースレッドにリクエストキューを分けておくこと。そこでは同じプライオリティなら first in, first out になります。

Q. Activity 間でリクエストを受け渡しできるようにする予定はありますか?
A. もしケースをするとしたら、リクエストの URL か、parcelable なメタデータを渡すと思います。

Q. Activity に遷移するまえにリクエストが入っていたら?
A. あぁ、つまり static などこかに押し込むというオプションのことですね。いい答えはないと思います。

Q. セッションのサンプルコードはすべて JSON リクエストとレスポンスを使っていたけれど、XML などの別の protocol タイプを使った例はありますか?
A. すいません、言うの忘れてました。Play Store では 普遍的にプロトコルバッファーを使っており、Play Store は Volley を使っています。もちろん XML も使えます。我々は protobuf を使っています。

Q. リクエストに失敗したときのサポートはありますか?
A. もちろんあります。リトライポリシーをカスタマイズすることもできます。バックオフアルゴリズムをセットしたり。



0 件のコメント:

コメントを投稿