非同期処理でよく使う IntentService と AsyncTask は何が違って何が同じなのか

非同期処理と言えば、`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`が頑張るので安心してね、と言う用になっています。