Edited at

Androidの非同期処理の話

More than 1 year has passed since last update.

非同期処理でよく使う IntentService と AsyncTask は何が違って何が同じなのかという記事を読んだので。


HandlerThreadについて


HandlerThreadは、内部に持っているHandlerにメッセージが渡ってきた時、それを順に処理するようできているので、メッセージを同時に複数送ると、ジョブキューのようにシリアルな動作で、メッセージを1つずつ捌いていきます。


これはHandlerThreadのコードを読むのが早いのですが(凄く短いので)、このクラスはイベントループに紐付けるための定型処理のある、ただのThreadのサブクラスです。なので、内部に持っているのは、Handler(メッセージ)ではなく、Looper(イベントループ)です。

Handlerは頻繁に使われますが、Looperはそれほど意識されません。なぜならメインスレッド(=UIスレッド)は、アプリケーションのメインループに自動的に紐付けられているからです。

対して、明示的に生成したスレッドに対してメッセージを送りたい場合には、Looper.prepare()を呼び出すことでイベントループを準備する必要があります。


BroadcastReceiverでAsyncTask

onReceive()はメインスレッドで動作し、10秒でタイムアウトします。

通常、AsyncTaskを使うという発想は出てこないと思います。


AsyncTaskLoaderについて


処理結果を受け取るコールバックの管理の煩雑さを軽減しようとしてAsyncTaskをLoaderでラップしただけの存在です。本当にありがとうございました。


リファレンスを読むとそう取れますが、実際にはAsyncTaskLoaderAsyncTaskのラッパーではありません。ModernAsyncTaskというプライベートクラスを利用しています。

これは2.X系とそれ以外とでAsyncTaskの挙動が異なるのを吸収するためだと思われます。AsyncTaskの非同期処理を直列か並列か切り替えることのできるAPIは存在しますが、3.0以降でしか使えないのに対し、ModernAsyncTaskは常に5つのスレッドプールでパラレルに動作します。

ModernAsyncTaskという命名から、単なるAsyncTaskより優れた機能を持っているのかと思うところですが、むしろonProgressUpdate()に相当する処理などがない、必要最小限まで簡略化されたクラスです。非公開クラスなのはそのためです。


メモリ可視性について


とくにメモリ上の可視性についてよく面倒を見てくれます。


メモリ可視性というと、一般には別スレッドから値を読み取ったときに、最新の値であることを保証する…というような意味合いで使われると思うのですが、最新の値が値渡しされることをメモリ可視性の面倒を見てくれる、と言うんでしょうか?


非同期処理の使い分け

個人的な感想ですが。


Thread

単純な非同期処理であれば、Threadで十分な場合もあります。

Threadを明示的に生成し、必要に応じてHandlerでコールバックを受け取ります。コールバック先がメインスレッドでない場合、Looper.prepare()でイベントループを準備させなければなりません(HandlerThreadがやっていることと同じです)。

GoogleのGCMなどのソースコードなどを追うと良く見かける気がします。スレッドの制御が必要な場合はExecutorServiceを利用するべきです。


AsyncTask

UIスレッドと強く協調動作する必要性があり、Activityのライフサイクルで完結できる非同期処理の場合に向いています。

例えば複雑なデータをパースし、計算して、結果をグラフとして描画するなどです。リファレンスでは長くとも数秒で終わる処理に使うべき(should)とされています。

UIが絡む場合でも、ネットワーク経由で取得した画像を表示するなどのケースでは使わない方が無難な場合な気がします。例えば、画面を回転させただけでActivityは死にますが、その都度画像を再度ダウンロードすべきでしょうか?

イベントループやスレッドプールを意識することなく、UIに対するコールバックを含めた非同期処理を書けるAsyncTaskは非常に便利なヘルパークラスです。ただしUIスレッドへのコールバックを含めない非同期処理まで濫用すべきではありません。


AsyncTaskLoader

UIスレッドと協調動作する必要性があり、かつActivityやFragmentのライフサイクルとは無関係に動作させたい非同期処理に向いています。

AsyncTaskとの違いは動作にUIスレッドを要求しないことです。

LoaderはActivityやFragmentがそれぞれ一つだけ持つLoaderManagerによって管理され、コールバックインターフェースを用いて結果を受け取ります。この疎結合なインターフェースによって、非同期処理がActivityと不可分になってしまうことを防ぎます。

その代わり、進捗を細かく受け取る必要があるような、UIスレッドと密接に関わる処理には向きません。


IntentService

UIスレッドと関連付ける必要性が乏しく、かつActivityのライフサイクルとは無関係に動作させたい、ある程度大きな粒度のタスクに向きます。

IntentServiceの前に、サービスについてよくある勘違いを正しておくと、サービスは単にUIを持たないコンポーネントであって、別スレッドや別プロセスで動作するものではありません。通常、Serviceはホストプロセスのメインスレッドで動作します。

もしサービスでバックグラウンドで処理を行いたい場合、明示的にスレッドを生成する必要があります。また、その処理の完了を持ってサービスを停止するなど、サービスのライフサイクル管理も適切に行う必要があります。

そうした煩雑さを解消するためにIntentServiceがあります。ワーカースレッドを作成し、Intentで受け取ったタスクをキューで処理し、完了したらサービスを停止してくれるのです。至れり尽せりです。


Fragment

UIスレッドと関連付ける必要性が乏しく、しかしActivityのライフサイクルと合致したバックグラウンドタスクの場合、不可視のFragmentが利用できます。

この場合、実装としては単純にThreadExecutorServiceを使う場合と同じですが、ライフサイクルメソッドを利用して処理の中断やリソースの解放を行えること、再利用性が高まるというメリットがあります。


まとめ

Androidにおいても非同期プログラミングはThreadExecutorServiceなどJavaの知識で行えます。

Androidフレームワーク特有の問題を解消するために、UIスレッドにコールバックするためのAsyncTaskや、Activityを持たずにバックグラウンド処理を行うためのIntentServiceなどのヘルパークラスが用意されています。

Activityのライフサイクルを超えるような長いバックグラウンド処理は、ServiceExecutorServiceを利用できますが、現在ではLoaderを利用するという手段もあります。逆にActivityのライフサイクルと合致するバックグラウンド処理は、Fragmentで書くこともできます。