非同期処理と言えば、IntentService
とAsyncTask
のいずれかは使ったことがあると思います。UI スレッドとは別のところで非同期に何かする、という点ではどちらも同じことをこなせるのですが、実際問題として何が違って何が同じなのかというところを踏み込んで見て行きたいと思います。
並行性とパフォーマンス
IntentService
は内部にHandlerThread
を持っていて、このHandlerThread
のなかで非同期処理を実行します。HandlerThread
は、内部に持っているHandler
にメッセージが渡ってきた時、それを順に処理するようできているので、メッセージを同時に複数送ると、ジョブキューのようにシリアルな動作で、メッセージを1つずつ捌いていきます。
Handler
がもっているメッセージキューにメッセージがなくなると、IntentService
は自らのライフサイクルを終わらせます。
AsyncTask
は内部にExecutor
を使用しており、スレッドプールにあるスレッドを使いまわして非同期処理を実行します。AsyncTask
のインスタンスを複数作って個別に非同期処理を実行しても、スレッドプールは共通のものが使われます。
Android のバージョンによってExecutor
の動作が異なり、DONUT ~ GINGER_BREAD ではパラレルに動作し、それ以外ではシリアルに動作します。ただしこれはデフォルトでそうなっているだけなので、プログラマが任意にこれを変えることも出来ます。
実際計測してみないと分からない部分もありますが、IntentService
のライフサイクルが一巡する回数が多くなると、その分だけIntentService
の起動や、内部のHandlerThread
の準備に取られる時間が増えるので、IntentService
の輪廻転生が激しく継続するような状況の場合には、AsyncTask
の方がThread
の準備にある程度のアドバンテージがあるのでパフォーマンスが良さそうに見えます。
コールバック
AsyncTask
もIntentService
も、最低限非同期処理の内容だけを実装すればよい作りになっているので、そもそも処理結果を必要としない場合はどちらにしても同じです。
処理結果を受け取るコールバックが必要な場合には、以下の様な違いが現れます。
IntentService
の場合、コールバックを受けるContext
とIntentService
のContext
は完全に別物ですので、Broadcast Intent
等で受け取れるコンポーネントに受け取らせるような仕組みで十分うまく動くはずです。
AsyncTask
の場合、ActivityContext
に深く紐付いて動作し、多くの場合コールバックはActivityContext
と共にその寿命を迎えるので、ActivityContext
が生きているかどうかに注意しなければならなくなります。
ここが1つの大きな差異となって現れる部分で、IntentService
はアプリケーションの中のコンポーネントとして動作するため、プロセス間での協調ができます。一方で、AsyncTask
はActivityContext
に紐付いて動作するものなので、プロセス間でどうこう、という想定はありません。
インスタンスの使いまわし
そもそもインスタンスの生成からして全く異なり、IntentService
はシステムが勝手にやってくれ、AsyncTask
は自分で生成します。
IntentService
はHandler
のメッセージキューにメッセージがあり続ける限り生き続けるので、その間にいくらメッセージを送ろうとも、メモリ上には1つのIntentService
があるだけです。
AsyncTask
の場合は、同じインスタンスを使いまわすようには出来ていないので、処理単位でインスタンスをガンガン作っていかないといけません。
ところで
AsyncTaskLoader
ってどうなのよ、という話ですが、これはAsyncTask
をLoader
の仕組みの中で管理し、処理結果を受け取るコールバックの管理の煩雑さを軽減しようとしてAsyncTask
をLoader
でラップしただけの存在です。本当にありがとうございました。
IntentService
でAsyncTask
は使えない
もともと、AsyncTask
で非同期に処理した結果はHandler
を介してAsyncTask
を初期化したスレッドに戻るようになっています。より正しくは、Handler
(Looper
)が必要なのでHandlerThread
に戻ります。そうすると、IntentService
はHandlerThread
を使っているので、AsyncTask
の結果が受け取れるように見えますが、IntentService
がライフサイクルを終えるとHandlerThread
も死んでしまうので、AsyncTask
の結果の戻りで「死体蹴りしてるぞ!(sending message to a Handler on a dead thread
)」という警告ログが出力されてしまいます。この辺りは、GCM の初期のサンプルコードに関連するところで話題になりました。
AsyncTask
クラスのロードのタイミングを早めることで回避する、という裏ワザが紹介されたこともありましたが、全くの非推奨な方法となり、JellyBean 以降、強制的にAsyncTask
は必ずメインスレッドでクラスをロードするようになり、また、ガイドラインとしてメインスレッドで初期化し、メインスレッドでexecute()
するようになっています。
その意味では、IntentService
がIntentService
を呼び出しても、関係ない別のContext
にメッセージを送るだけのことなので、特に問題となるようなことは起こらないはずです。
BroadcastReceiver
でAsyncTask
は使えない
BroadcastReceiver#onReceive(Context, Intent)
の中で非同期処理をする場合にもIntentService
を使用します。これは、BroadcastReceiver
はonReceive
を抜けた瞬間に死んでしまうからで、BroadcastReceiver
で結果を受け取ろうとしている状況でAsyncTask
の処理が終わる頃には、BroadcastReceiver
はもう死んでしまっているかもしれないから使えない、ということです。
AsyncTask は同期化の面倒を見てくれる
とくにメモリ上の可視性についてよく面倒を見てくれます。
AsyncTask guarantees that all callback calls are synchronized in such a way that the following operations are safe without explicit synchronizations.
- Set member fields in the constructor or onPreExecute(), and refer to them in doInBackground(Params...).
- Set member fields in doInBackground(Params...), and refer to them in onProgressUpdate(Progress...) and onPostExecute(Result).
とあるように、フィールドの変数に関して、明示的に同期化を頑張らなくても、AsyncTask
が頑張るので安心してね、と言う用になっています。