1. ホーム
  2. アンドロイド

AndroidにおけるAsyncTaskの使用方法について説明します。

2022-03-02 19:56:06

Androidで非同期タスクの仕組みを実装するには、HandlerとAsyncTaskの2つの方法があります。

Handlerモードでは、タスクごとに新しいスレッドを作成し、タスク完了後にHandlerインスタンスを通じてUIスレッドにメッセージを送信し、インターフェースの更新を完了させる必要があります。この方法は、プロセス全体をより細かく制御できますが、コードが比較的肥大化し、複数のタスクが同時に実行された場合にスレッドを正確に制御することが容易ではないなどのデメリットもあります。 ハンドラについて知っておくべきこと も先に説明しましたので、よくわからないという方のために説明します。

Android 1.5 では、操作を簡単にするために android.os.AsyncTask というツールクラスが用意されており、これを使うと非同期タスクが簡単に作成でき、同じタスクを達成するためにタスクスレッドと Handler インスタンスを記述する必要がなくなります。

まず、AsyncTaskの定義から説明します。

public abstract class AsyncTask<Params, Progress, Result> {

3つの汎用型は、quot;タスク実行開始のための入力パラメータ、quot;バックグラウンドタスク実行の進捗状況、quot;バックグラウンド計算結果の種類を表します。すべての型がある状況で使われるとは限りませんし,使われない場合はjava.lang.Dataで代用することができます.

非同期タスクの実行は、通常、以下の手順で構成される。

1. execute(Params... params) 非同期タスクを実行するには、コード内でこのメソッドを呼び出して、非同期タスクの実行をトリガーする必要があります。

2. onPreExecute() これは execute(Params... params) が呼ばれた直後に実行され、一般にバックグラウンドタスクの実行前にUIのマークアップを行うために使用されます。

3. doInBackground(Params... params) このメソッドは、onPreExecute()が完了した直後に実行され、入力パラメータを受け取り、計算結果を返すという、より時間のかかる処理を行うために使用される。実行中に、publishProgress(Progress... values)を呼び出すと、進捗情報を更新することができます。 values)を呼び出すと、実行中に進捗情報を更新することができます。

4. onProgressUpdate(Progress... values) publishProgress(Progress... values)が呼ばれた時にこのメソッドが実行され、進捗情報を直接UIコンポーネントに更新します。 value)が呼ばれた時にこのメソッドが実行され、進捗情報を直接UIコンポーネントに更新します。

5. onPostExecute(Result 結果) このメソッドはバックグラウンドでの演算が終了すると呼び出され、演算結果がこのメソッドのパラメーターとして渡され、UIコンポーネントに直接結果が表示されます。

これを使用する場合、いくつかの注意点があります。

1. 非同期タスクのインスタンスは、UIスレッドで作成する必要があります。

2. execute(Params... params) メソッドをUIスレッドで呼び出すこと。

3. onPreExecute(), doInBackground(Params... params), onProgressUpdate(Progress... values), and onPostExecute(Result result) メソッドを手動で呼び出さないこと。

4. doInBackground(Params... params)で、UIコンポーネントの情報を変更することはできません。

5. タスクインスタンスは一度しか実行できず、二度目に実行されると例外が発生します。

次に、AsyncTaskを使った非同期タスクの実行方法について説明します。まず、以下のような構成のプロジェクトを作成します。

比較的シンプルな構成なので、まずはMainActivity.javaのコードから見ていきましょう。

package com.scott.async;

import java.io.ByteArrayOutputStream;
import java.io;

import org.apache.http.HttpEntity;
import org.apache.http;
HttpStatus; import org.apache.http;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;

import android.app.Activity;
import android.os.AsyncTask;
import android.os;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.ProgressBar;
import android.widget.TextView;

public class MainActivity extends Activity {

    private static final String TAG = "ASYNC_TASK";

    private Button execute;
    private Button cancel;
    private ProgressBar progressBar;
    private TextView textView;

    private MyTask mTask;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        execute = (Button) findViewById(R.id.execute);
        execute.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // note that each time you need to new an instance, the new task can only be executed once, otherwise an exception will occur
                mTask = new MyTask();
                mTask.execute("http://www.baidu.com");

                execute.setEnabled(false);
                cancel.setEnabled(true);
            }
        });
        cancel = (Button) findViewById(R.id.cancel);
        cancel.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // To cancel a task in progress, the onCancelled method will be called
                mTask.cancel(true);
            }
        });
        progressBar = (ProgressBar) findViewById(R.id.progress_bar);
        textView = (TextView) findViewById(R.id.text_view);

    }

    private class MyTask extends AsyncTask<String, Integer, String> {
        //onPreExecute method is used to do some UI operations before executing the background task
        @Override
        protected void onPreExecute() {
            Log.i(TAG, "onPreExecute() called");
            textView.setText("loading... ");
        }

        //doInBackground method executes background tasks internally, no UI changes in this method
        @Override
        protected String doInBackground(String... params) {
            Log.i(TAG, "doInBackground(Params... params) called");
            try {
                HttpClient client = new DefaultHttpClient();
                HttpGet get = new HttpGet(params[0]);
                HttpResponse response = client.execute(get);
                if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) { if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK)
                    HttpEntity entity = response.getEntity();
                    InputStream is = entity.getContent();
                    long total = entity.getContentLength();
                    ByteArrayOutputStream baos = new ByteArrayOutputStream();
                    byte[] buf = new byte[1024];
                    int count = 0;
                    int length = -1;
                    while ((length = is.read(buf)) ! = -1) {
                        baos.write(buf, 0, length);
                        count += length;
                        // Call publishProgress to publish the progress, and finally the onProgressUpdate method will be executed
                        publishProgress((int) ((count / (float) total) * 100));
                        // To demonstrate the progress, sleep for 500 milliseconds
                        Thread.sleep(500);
                    }
                    return new String(baos.toByteArray(), "gb2312");
                }
            } catch (Exception e) {
                Log.e(TAG, e.getMessage());
            }
            return null;
        }

        //onProgressUpdate method is used to update progress information
        @Override
        protected void onProgressUpdate(Integer... progresses) {
            Log.i(TAG, "onProgressUpdate(Progress... progresses) called");
            progressBar.setProgress(progresses[0]);
            textView.setText("loadin

レイアウトファイルmain.xmlのコードは次のとおりです。

<?xml version="1.0" encoding="utf-8"? >
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent">
    <Button
    	android:id="@+id/execute"
    	android:layout_width="fill_parent"
    	android:layout_height="wrap_content"
    	android:text="execute"/>
    <Button
    	android:id="@+id/cancel"
    	android:layout_width="fill_parent"
    	android:layout_height="wrap_content"
    	android:enabled="false"
    	android:text="cancel"/>
	<ProgressBar 
	    android:id="@+id/progress_bar" 
	    android:layout_width="fill_parent" 
	    android:layout_height="wrap_content" 
	    android:progress="0"
	    android:max="100"
	    style="?android:attr/progressBarStyleHorizontal"/>
	<ScrollView
	    android:layout_width="fill_parent" 
	    android:layout_height="wrap_content">
		<TextView
		    android:id="@+id/text_view"
		    android:layout_width="fill_parent" 
		    android:layout_height="wrap_content"/>
	</ScrollView>
</LinearLayout>


ネットワークにアクセスする必要があるため、AndroidManifest.xmlの以下の箇所でネットワークへのアクセスを追加する必要があります。

<uses-permission android:name="android.permission.INTERNET"/>

ランタイム・インターフェイスを見てみよう。

上記のスクリーンショットは、初期画面、非同期タスク実行時の画面、実行成功後の画面、タスクキャンセル後の画面です。実行に成功した場合、全体のプロセスログは以下のように表示されます。

タスクの実行中に"cancel"ボタンを押すと、次のようなログが出力されます。

onCancelled() メソッドが呼び出され、onPostExecute(Result result) メソッドが呼び出されなくなることがわかります。

以上、AsyncTaskの基本的な使い方を説明しました。AsyncTaskが内部でどのように実行されるのか、Handlerの使い方とどう違うのか、気になる方もいらっしゃると思います。答えはこうです。AsyncTaskはThread+Handlerの良いカプセル化であり、android.osにThreadとHandlerの痕跡を見ることができます。以下は、AsyncTaskがどのように実行されるかの詳細な説明です。

AsyncTaskのアウトラインビューを見てみましょう。

doInBackground(Params... params)はAsyncTaskを継承する際にオーバーライドしなければならない抽象メソッド、onPreExecute(), onProgressUpdate(Progress... values), onPostExecute(Result result). values), onPostExecute(Result result), onCancelled() は全て空のメソッドなので必要なら任意にオーバーライドできる; publishProgress(Progress...) は、AsyncTaskを継承した際に上書きしなければならない。 ...values)はfinalなのでオーバーライドできず、呼び出すだけです。このメソッドは通常doInBackground(Params... params)で呼び出します。さらに、Status列挙クラスとgetStatus()メソッドがあり、Status列挙クラスのコードスニペットは次の通りです。

//Initial Status
private volatile Status mStatus = Status.PENDING;

public enum Status {
    PENDING; public enum Status { /**
     * Indicates that the task has not been executed yet.
     */PENDING, PENDING, PENDING.
    PENDING,
    /**
     * Indicates that the task is running.
     */
    RUNNING,
    /**
     * Indicates that {@link AsyncTask#onPostExecute} has finished.
     */
    FINISHED,
FINISHED, */}

/**
 * Returns the current status of this task.
 *
 * @return The current status.
 */
public final Status getStatus() {
    return mStatus;
}

このように、AsyncTaskの初期状態はPENDINGで保留状態、RUNNINGは実行状態、FINISHEDは終了状態を表しており、これらはAsyncTaskのライフタイム中に様々な場所で使われる、とても重要なものです。

アウトラインビューを紹介したら、次はexecute(Params... params)を入り口にして、AsyncTaskの実行フローに注目してみます。 params)メソッドのコードスニペットを見てみましょう。

public final AsyncTask<Params, Progress, Result> execute(Params... params) {
    if (mStatus ! = Status.PENDING) {
        switch (mStatus) {
            case RUNNING:
                //throw an exception if the task is being executed
                //It is worth mentioning that after calling cancel the task, the status is still not RUNNING
                throw new IllegalStateException("Cannot execute task:"
                        + " the task is already running.");
            case FINISHED:
                //Throw an exception if the task has already finished
                throw new IllegalStateException("Cannot execute task:"
                        + " the task has already been executed "
                        + "(a task can be executed only once)");
        }
    }

    //Change status to RUNNING
    mStatus = Status.RUNNING;

    //call the onPreExecute method
    onPreExecute();

    mWorker.mParams = params;
    sExecutor.execute(mFuture);

    return this;
}

このコードには、mWorker、sExecutor、mFutureという見慣れない変数が含まれていますが、その正体についても見ていきましょう。

sExecutorについてですが、これはjava.util.concurrent.ThreadPoolExecutorのインスタンスで、スレッドの実行を管理するために使用されます。コードは以下の通りです。

private static final int CORE_POOL_SIZE = 5;
private static final int MAXIMUM_POOL_SIZE = 128;
private static final int KEEP_ALIVE = 10;

// Create a new queue to store threads
private static final BlockingQueue<Runnable> sWorkQueue =
        new LinkedBlockingQueue<Runnable>(10);
// Create a new thread factory
private static final ThreadFactory sThreadFactory = new ThreadFactory() {
    private final AtomicInteger mCount = new AtomicInteger(1);
    // Create a new thread
    public Thread newThread(Runnable r) {
        return new Thread(r, "AsyncTask #" + mCount.getAndIncrement());
    }
};
// Create a new thread pool executor to manage the execution of threads
private static final ThreadPoolExecutor sExecutor = new ThreadPoolExecutor(CORE_POOL_SIZE,
        MAXIMUM_POOL_SIZE, KEEP_ALIVE, TimeUnit.SECONDS, sWorkQueue, sThreadFactory);

mWorkerは、実際にはAsyncTaskの抽象内部クラス実装のインスタンスで、Callable<Result>インターフェースのcall()メソッドを実装しており、以下のコードになっています。

private static abstract class WorkerRunnable<Params, Result> implements Callable<Result> {
    Params[] mParams;
}

そして、mFutureは実はjava.util.concurrent.FutureTaskのインスタンスで、以下はそのFutureTaskクラスの情報です。

/**
 * A cancellable asynchronous computation.
 * ...
 */
public class FutureTask<V> implements RunnableFuture<V> {



public interface RunnableFuture<V> extends Runnable, Future<V> {
    /**
     * Sets this Future to the result of its computation
     * unless it has been cancelled.
     */
    void run();
}

FutureTaskは、途中でキャンセルできる非同期計算のためのクラスであることがわかります。

以下は、AsyncTaskの中でmWorkerとmFutureのインスタンスがどのように表現されるかを示しています。

private final WorkerRunnable<Params, Result> mWorker;
private final FutureTask<Result> mFuture;

public AsyncTask() {
    mWorker = new WorkerRunnable<Params, Result>() {
        //after the call method is called, the priority will be set to the background level, and then the doInBackground method of the AsyncTask will be called
        public Result call() throws Exception {
            Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
            return doInBackground(mParams);
        }
    };

    //In the mFuture instance, the mWorker will be called to do the background task, and the done method will be called when it's done
    mFuture = new FutureTask<Result>(mWorker) {
        @Override
        protected void done() {
            Message message;
            Result result = null;

            try {
                result = get();
            } catch (InterruptedException e) {
                android.util.Log.w(LOG_TAG, e);
            } catch (ExecutionException e) {
                throw new RuntimeException("An error occured while executing doInBackground()",
                        e.getCause());
            } catch (CancellationException e) {
                // send a message to cancel the task
                message = sHandler.obtainMessage(MESSAGE_POST_CANCEL,
                        new AsyncTaskResult<Result>(AsyncTask.this, (Result[]) null));
                message.sendToTarget();
                return;
            } catch (Throwable t) {
                throw new RuntimeException("An error occured while executing "
                        + "doInBackground()", t);
            }

            // send a message showing the result
            message = sHandler.obtainMessage(MESSAGE_POST_RESULT,
                    new AsyncTaskResult<Result>(AsyncTask.this, result));
            message.sendToTarget();
        }
    };
}

上記のコードでは、mFuture インスタンスオブジェクトの do() メソッドは、CancellationException タイプの例外をキャッチした場合は "MESSAGE_POST_CANCEL"、正常に実行した場合は "MESSAGE_POST_RESULT" メッセージを送信していることが分かります。実行に成功した場合、"MESSAGE_POST_RESULT" メッセージを送信し、メッセージは sHandler オブジェクトと関連付けられます。このsHandlerのインスタンスは、実はHandlerを継承したAsyncTaskの内部クラスInternalHandlerのインスタンスですので、そのコードを解析してみましょう。

private static final int MESSAGE_POST_RESULT = 0x1; //Display results
private static final int MESSAGE_POST_PROGRESS = 0x2; //Update progress
private static final int MESSAGE_POST_CANCEL = 0x3; //Cancel the task

private static final InternalHandler sHandler = new InternalHandler();

private static class InternalHandler extends Handler {
    @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
                // Call AsyncTask.finish method
                result.mTask.finish(result.mData[0]);
                break;
            case MESSAGE_POST_PROGRESS:
                // Call AsyncTask.onProgressUpdate method
                result.mTask.onProgressUpdate(result.mData);
                break;
            case MESSAGE_POST_CANCEL:
                // call AsyncTask.onCancelled method
                result.mTask.onCancelled();
                break;
        }
    }
}


メッセージが処理されて "MESSAGE_POST_RESULT" が発生すると、AsyncTask の finish() メソッドを呼び出していることがわかりますので、その定義を見てみましょう。

private void finish(Result result) {
    if (isCancelled()) result = null;
    onPostExecute(result); // call onPostExecute to display the result
    mStatus = Status.FINISHED; //change the status to FINISHED
}

つまり、finish() メソッドは onPostExecute(Result result) メソッドを呼び出して結果を表示し、タスクの状態を変更する役割を担っているのです。

また、mFutureオブジェクトのdo()メソッドで、メッセージを構築すると、メッセージにはAsyncTaskResult型のオブジェクトが含まれ、sHandlerインスタンスオブジェクトのhandleMessage(Message msg)メソッドでは、以下のような方法でメッセージに付随するオブジェクトを取得します。

AsyncTaskResult result = (AsyncTaskResult) msg.obj;

このAsyncTaskResultとは一体何なのか、何が入っているのか?これもAsyncTaskの内部クラスで、実行結果をラップしているクラスなので、そのコード構造を見てみましょう。

@SuppressWarnings({"RawUseOfParameterizedType"})
private static class AsyncTaskResult<Data> {
    final AsyncTask mTask;
    final Data[] mData;

    AsyncTaskResult(AsyncTask task, Data... data) {
        mTask = task;
        mData = data;
    }
}

この AsyncTaskResult は AsyncTask のインスタンスと何らかの型のデータセットをカプセル化していることがわかるので、メッセージを作成するためのコードを見てみましょう。

//Send a message to cancel the task
message = sHandler.obtainMessage(MESSAGE_POST_CANCEL,
        new AsyncTaskResult<Result>(AsyncTask.this, (Result[]) null));
message.sendToTarget();
//Send the message that shows the result
message = sHandler.obtainMessage(MESSAGE_POST_RESULT,
         new AsyncTaskResult<Result>(AsyncTask.this, result));
message.sendToTarget();

result.mTask.finish(result.mData[0]);
result.mTask.onProgressUpdate(result.mData);

メッセージを処理するときに、このオブジェクトはどのように使われるのでしょうか、もう一度見てみましょう。

result.mTask.finish(result.mData[0]);

result.mTask.onProgressUpdate(result.mData);

おさらいすると、execute(Params... params) メソッドを呼ぶと、execute メソッドは onPreExecute() メソッドを呼び、ThreadPoolExecutor インスタンス sExecutor は FutureTask タスクを実行し、その間に開発者が doInBackground(Params... params) を上書きすると doInBackground( Params) が呼ばれることになります。 ...params)メソッドをオーバーライドすると、publishProgress(Progress... values)メソッドを呼び出します。 values)メソッドでは、進捗を更新するためにMESSAGE_POST_PROGRESS メッセージが InternalHandlerインスタンスのsHandlerを通して送られ、 onProgressUpdate(Progress... values)が呼び出されます。 ... values)メソッドが、sHandlerがメッセージを処理する際に呼び出されます。例外が発生した場合、タスクをキャンセルするためにMESSAGE_POST_CANCELメッセージが送られ、onCancelled()メソッドがsHandlerがメッセージを処理する際に呼ばれます。 実行が成功した場合、結果を表示するためにMESSAGE_POST_RESULTメッセージが送られ onPostExecute(Result result) メソッドが sHandlerがメッセージを処理する際に呼び出されます。

以上の紹介を経て、Thread+Handlerをうまくカプセル化して、開発者の対応の複雑さを軽減し、開発効率を向上させるAsyncTaskの本質を友人が実感してくれたと思うので、友人にはもっと体験してもらいたいと思います。