最近は非同期処理の殆どをRxJava使って楽してるので、AsyncTaskの事はすっかり忘れてしまっていたのですが、改めて調べてみたので、メモ程度ですがご紹介します。(API Lebel 23の実装を前提に書いています。古いバージョンでは実装が異なる部分もあるそうです。 AndroidバージョンでのAsyncTaskの注意点 )
概要
JavaDocのClassOverViewには要約するとこんなことが書かれていました
AsyncTaskはバックグラウンドで処理を実行し、ThreadまたはHandlerを操作することなくUIスレッドに結果を公開することが出来ます。
AsyncTaskはThreadやHandlerを扱うためのヘルパークラスとしてデザインされており、一般的なスレッドフレームワークとして作られていません。
AsyncTasksは最大で数秒程度の処理時間のタスクに利用すべきで、長時間実行中のスレッドを維持しておきたいなら `java.util.concurrent`パッケージのThreadPoolExecutorとFutureTaskなどを利用することをおすすめします。
長時間かかる処理にAsyncTaskを利用すべきでないということは確かによく耳にしますね。
基本的な使い方
AsyncTaskはabstruct classで最低限2つの事を行う必要があります。
- abstruct methodの
AsyncTask#doInBackground
に非同期で実行したい処理を記述する。 -
AsyncTask#execute
callして非同期処理を実行する。
new AsyncTask<String, Integer, Long>() {
@Override
protected Long doInBackground(String... params) {
// ここにバックグラウンドで行いたい処理を記述し、結果をreturnする。
return 2l;
}
}.execute("wow","wow"); //非同期処理に対して引数を渡す。
AsyncTask#execute
の引数はdoInBackground
に可変長引数の形で渡されます。そしてdoInBackground
の処理は、AsyncTaskによってworkerスレッド(UIスレッド以外のスレッド)内で実行されます。
結果を受け取りたい場合はonPostExecute
をオーバーライドすることでUIスレッド上で結果を受け取ることができます。他にもオプショナルな機能として、UIスレッドに進捗を返すことが出来るonProgressUpdate
やdoInBackground
の実行前に事前処理を行うonPreExecute
もあります。
doInBackgroundの処理が非同期に実行される仕組み
AsyncTaskがどのように非同期処理を実現しているのか実装を追ってみます。
まずAsyncTaskのコンストラクタの中で、doInBackground
の処理はFutureTask化されます。FutureTaskについては正しい説明が出来る自信はないですが、要は「Runnable
でラップして、後に出てくるThreadPoolExecutorに渡せる形に変換している」という理解でとりあえず問題ないと思います。 簡略化したコンストラクタが以下のようになります。
public AsyncTask() {
mWorker = new WorkerRunnable<Params, Result>() {
public Result call() throws Exception {
Result result = doInBackground(mParams);
return postResult(result);
}
};
mFuture = new FutureTask<Result>(mWorker) {
@Override
protected void done() {
postResultIfNotInvoked(get());
}
};
}
ここまでが、AsyncTaskのインスタンスが生成された時に行われる下準備です。
ここからは実行時の処理です。AsyncTask#execute
がcallされることでAsyncTask#executeOnExecutor
が内部で呼ばれます。executeOnExecutor
の第一引数であるsDefaultExecutor
はThreadPoolExecutor
という管理下のスレッドプールでRunnableを実行するクラスと、実行するRunnable貯めておくスタックを内包しているSerialExecutor
というクラスです。
executeOnExecutor内では、Executor#exec(Runnable)
によって、事前にFutureTask化されたdoInBackground
がworkerスレッドで実行されます。このような仕組みでdoInBackground
が非同期処理を実現していたんですね。
public final AsyncTask<Params, Progress, Result> execute(Params... params) {
return executeOnExecutor(sDefaultExecutor, params);
}
public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec, Params... params) {
onPreExecute();
mWorker.mParams = params;
exec.execute(mFuture);
return this;
}
ちなみにdoInBackground
の実行結果がUIスレッドに戻ってこれるのは、doInBackground
をFutureTask化した際に、最後にpostResult(result)
を呼ぶようになっていて、内部のInternalHandler
というHandlerにメッセージを送信され、それ経由でAsyncTask#onPostExecute(result)
がコールされるためです。
private void finish(Result result) {
if (isCancelled()) {
onCancelled(result);
} else {
onPostExecute(result);
}
mStatus = Status.FINISHED;
}
private static class InternalHandler extends Handler {
public InternalHandler() {
super(Looper.getMainLooper());
}
@SuppressWarnings({"unchecked", "RawUseOfParameterizedType"})
@Override
public void handleMessage(Message msg) {
AsyncTaskResult<?> result = (AsyncTaskResult<?>) msg.obj;
switch (msg.what) {
case MESSAGE_POST_RESULT:
// There is only one result
result.mTask.finish(result.mData[0]);
break;
case MESSAGE_POST_PROGRESS:
result.mTask.onProgressUpdate(result.mData);
break;
}
}
}
おわり
結局、なぜ長時間の処理に向いてない理由がよくわからなかったので、あとで調べて追記したいと思います。
AsyncTask内部のThreadPoolExecutorはプール数がCPU数+1、KeepAliveTimeが1sで設定されています。なので例えばPool数を超えた処理が流れてきた場合、1sを超えた現在実行中のタスクは終了を待たず止められてしまう可能性があるからおすすめできないということでした。こちらも詳細はThreadPoolExecutorのJavadocを御覧ください
複雑な部分は読み飛ばしてしまいましたが、AsyncTaskの実装を読んでみるのは意外と面白かったです。次はAsyncTaskLoaderも覗いてみたいですね。