131
133

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Last updated at Posted at 2014-09-04

非同期処理と言えば、IntentServiceAsyncTaskのいずれかは使ったことがあると思います。UI スレッドとは別のところで非同期に何かする、という点ではどちらも同じことをこなせるのですが、実際問題として何が違って何が同じなのかというところを踏み込んで見て行きたいと思います。

並行性とパフォーマンス

IntentServiceは内部にHandlerThreadを持っていて、このHandlerThreadのなかで非同期処理を実行します。HandlerThreadは、内部に持っているHandlerにメッセージが渡ってきた時、それを順に処理するようできているので、メッセージを同時に複数送ると、ジョブキューのようにシリアルな動作で、メッセージを1つずつ捌いていきます。
Handlerがもっているメッセージキューにメッセージがなくなると、IntentServiceは自らのライフサイクルを終わらせます。

AsyncTaskは内部にExecutorを使用しており、スレッドプールにあるスレッドを使いまわして非同期処理を実行します。AsyncTaskのインスタンスを複数作って個別に非同期処理を実行しても、スレッドプールは共通のものが使われます。
Android のバージョンによってExecutorの動作が異なり、DONUT ~ GINGER_BREAD ではパラレルに動作し、それ以外ではシリアルに動作します。ただしこれはデフォルトでそうなっているだけなので、プログラマが任意にこれを変えることも出来ます。

実際計測してみないと分からない部分もありますが、IntentServiceのライフサイクルが一巡する回数が多くなると、その分だけIntentServiceの起動や、内部のHandlerThreadの準備に取られる時間が増えるので、IntentServiceの輪廻転生が激しく継続するような状況の場合には、AsyncTaskの方がThreadの準備にある程度のアドバンテージがあるのでパフォーマンスが良さそうに見えます。

コールバック

AsyncTaskIntentServiceも、最低限非同期処理の内容だけを実装すればよい作りになっているので、そもそも処理結果を必要としない場合はどちらにしても同じです。

処理結果を受け取るコールバックが必要な場合には、以下の様な違いが現れます。

IntentServiceの場合、コールバックを受けるContextIntentServiceContextは完全に別物ですので、Broadcast Intent等で受け取れるコンポーネントに受け取らせるような仕組みで十分うまく動くはずです。

AsyncTaskの場合、ActivityContextに深く紐付いて動作し、多くの場合コールバックはActivityContextと共にその寿命を迎えるので、ActivityContextが生きているかどうかに注意しなければならなくなります。

ここが1つの大きな差異となって現れる部分で、IntentServiceはアプリケーションの中のコンポーネントとして動作するため、プロセス間での協調ができます。一方で、AsyncTaskActivityContextに紐付いて動作するものなので、プロセス間でどうこう、という想定はありません。

インスタンスの使いまわし

そもそもインスタンスの生成からして全く異なり、IntentServiceはシステムが勝手にやってくれ、AsyncTaskは自分で生成します。

IntentServiceHandlerのメッセージキューにメッセージがあり続ける限り生き続けるので、その間にいくらメッセージを送ろうとも、メモリ上には1つのIntentServiceがあるだけです。

AsyncTaskの場合は、同じインスタンスを使いまわすようには出来ていないので、処理単位でインスタンスをガンガン作っていかないといけません。

ところで

AsyncTaskLoaderってどうなのよ、という話ですが、これはAsyncTaskLoaderの仕組みの中で管理し、処理結果を受け取るコールバックの管理の煩雑さを軽減しようとしてAsyncTaskLoaderでラップしただけの存在です。本当にありがとうございました。

IntentServiceAsyncTaskは使えない

もともと、AsyncTaskで非同期に処理した結果はHandlerを介してAsyncTaskを初期化したスレッドに戻るようになっています。より正しくは、Handler(Looper)が必要なのでHandlerThreadに戻ります。そうすると、IntentServiceHandlerThreadを使っているので、AsyncTaskの結果が受け取れるように見えますが、IntentServiceがライフサイクルを終えるとHandlerThreadも死んでしまうので、AsyncTaskの結果の戻りで「死体蹴りしてるぞ!(sending message to a Handler on a dead thread)」という警告ログが出力されてしまいます。この辺りは、GCM の初期のサンプルコードに関連するところで話題になりました。

AsyncTaskクラスのロードのタイミングを早めることで回避する、という裏ワザが紹介されたこともありましたが、全くの非推奨な方法となり、JellyBean 以降、強制的にAsyncTaskは必ずメインスレッドでクラスをロードするようになり、また、ガイドラインとしてメインスレッドで初期化し、メインスレッドでexecute()するようになっています。

その意味では、IntentServiceIntentServiceを呼び出しても、関係ない別のContextにメッセージを送るだけのことなので、特に問題となるようなことは起こらないはずです。

BroadcastReceiverAsyncTaskは使えない

BroadcastReceiver#onReceive(Context, Intent)の中で非同期処理をする場合にもIntentServiceを使用します。これは、BroadcastReceiveronReceiveを抜けた瞬間に死んでしまうからで、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が頑張るので安心してね、と言う用になっています。

131
133
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
131
133

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?