すごい今更感ある内容ですが、 AsyncTaskの非同期処理はどのように実現しているのか
の続きみたいな記事です。
はじめに
Volleyは2013年のGoogle I/Oで発表され爆発的な人気を博しながらも、現在では少しだけ衰退気味のHttpクライアントですね。私の場合は、仕事のコードがvolleyラッパーのAPIクライアントを使っていて、趣味ではOkHttpをメインに使っています。Volleyが特徴的なのはHttpクライアントでありながら自身で非同期処理の仕組みを提供していることころです。本稿ではVolleyがどのように非同期処理を実現しているのかについて紹介します。細かい説明はかなり省いて、どちらかと言うとおおまかな設計を掴んで頂けるように努力します。
Volleyのコードのcloneする
よくわかってないのですが、このレポジトリ(Volley)からgit cloneするのが正しそうです。
そのまま既存のプロジェクトに取り込んでもいいですし、状況に応じてjarにしてプロジェクトにとり込みましょう。(なんで maven repoに上げないんですかね..)
非同期処理の主な登場人物
非同期処理の中核部分にいるのは、NetworkDispatcher
とPriorityBlockingQueue
です。PriorityBlockingQueueはjava.util.concurrent
で提供されているクラスで名前の通り優先度付きのBlocking付きのqueueですね。何がBlokingかというとqueueから値を受け取り時に新しいitemが入ってくるまで処理をブロックして待ち続けます。 NetworkDispatcherとはThreadを継承しているクラスなので、こいつが実質非同期処理を行うworkerスレッドの正体ということになります。
つまりPriorityBlockingQueueにRequestインタンスが続々と格納され、それをNetworkDispatcherが受け取ってネットワーク処理をバックグランドで行うということですね。思ってるほど難しくなく結構シンプルな構成ですね。NetworkDispatcher#run
(非同期処理の実行部分)は簡略するとこんな感じです。
@Override
public void run() {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
while (true) {
Request<?> request;
try {
request = mQueue.take(); // PriorityBlockingQueue(mQueue)から次のリクエストが受け取れるまでひたすら待つ
} catch (InterruptedException e) {
continue;
}
try {
Response<?> response = request.parseNetworkResponse(networkResponse);
mDelivery.postResponse(request, response); // ExecutorDelivery(mDelivery)経由でHandlerを呼び出して、UIスレッドに結果を返す。
} catch (Exception e) {
mDelivery.postError(request, volleyError); // 実行中に何か例外が発生した場合はその結果をUIスレッドに返す。
}
}
}
Volleyにおける並列処理の仕組み
NetworkDispatcher
は実質単一のスレッドなのでこれだけでは、httpリクエストの並行で捌くことは出来ません。なので一般的には内部にスレッドプールを用意していい感じに各スレッドに仕事を割り振る仕組みが必要です。Volleyでこのような役割をになっているのはRequestQueue
クラスです。RequestQueueはNetworkDispatcher[ ] mDispatchers
をメンバ変数に持ち、初期化のタイミングでNetworkDispatcherを生成します。つまりRequestQueue
とNetworkDispatcher
の組み合わせによってスレッドプールみたいな役割をします。またthreadのstartやstopも全てRequestQueue内で行われます。mDispatchersのサイズはRequestQueueのコンストラクタで指定され、デフォルト値が 4 になっています。適切なスレッド数というは難しく、端末の性能やネットワークの品質にもたぶん影響します。画像のダウンロードに関する適切なスレッド数の考察はモバイルアプリのスレッドプールサイズの最適化(画像読み込み編)の記事で詳しい説明があります。
おわり
内部実装読むのは楽しいですね。 VolleyはMarshmallowで消されてしまったorg.apache.http
パッケージに依存していることから最近ではオワコン感ありますが、実際ほとんどの依存はHurlStack
のみでHttpStack自体は外から入れ替え可能なのでHttpStackをOkhttpベースにするなどすれば別に使いつづけても問題ないような印象を受けました。まあでも、2016年なら普通にOkHttpをつかうといいとおもいます。非同期処理の機能(workerスレッドの管理/扱い)はOkHttpにはないので、AsyncTaskなりRxJavaなり使ったらいいんじゃないのでしょうか。ちなみにRxJavaはとても良いですよ。
ところで、実装を追っていてスレッドプール相当の部分の実装はjava.util.concurrent
のThreadPoolExecutorとか使って楽できそうな印象があったのですが、なぜ生のスレッドを管理しているんだろうという疑問がありました。実装はかなりななめ読みなので見落としてることや勘違いしてることがあるんでしょうか またわかったら追記しますね。