AsyncTask を利用した非同期処理

非同期処理と UI の更新 ではワーカースレッドを利用した非同期処理と UI を更新する時には Handler を利用して UI スレッドに対して処理をポストするという点について説明しました。

ここでは、そうしたバックグラウンドでの処理と UI とのやりとりをひっくるめて簡単に操作するための、 AsyncTask クラスの利用方法を説明します。

さらに、ついでに ProgressDialog のキャンセル処理とプログレスバーの表示についても説明します。

ProgressDialog と非同期処理のキャンセル

ここで作るサンプルプログラムは次のようなものです。まずはボタンが一つだけあります。

AsyncTask と ProgressDialog

このボタンをクリックすると、下のスクリーンショットのようにプログレスバー付きの ProgressDialog が表示されます。

AsyncTask と ProgressDialog

この進捗が100%になったところで、ダイアログは消えます。また、 BACK ボタンを押すことで処理をキャンセルすることもできることにします。

処理が進んだときの UI の更新とキャンセルの確認方法はどのようにしたらよいでしょうか。ここでは AsyncTask を利用してその辺の処理を簡易化します。

非同期処理の開始

まずは、メインのアクティビティでボタンをクリックした時の処理を書いてしまいます。ボタンをクリックしたときに非同期処理を開始しています。

package com.keicode.android.test;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;

public class AsyncTest2 extends Activity 
  implements OnClickListener {
  
  final String TAG = "AsyncTest2";
  
  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);
    ((Button)findViewById(R.id.button1))
      .setOnClickListener(this);
  }
  
  @Override
  public void onClick(View v) {
    if(v.getId() == R.id.button1){
      new MyAsyncTask(this).execute("Param1");
    }
  }
  
}

みてわかるように、クリックした時は MyAsyncTask というクラスのインスタンスを作って、それの execute メソッドを呼んでいます。 この execute には "Param1" という、サンプルプログラムらしい引数を渡しています(笑)。

この MyAsyncTask というのが、 後述の AsyncTask クラスを派生したユーザー定義のクラスです。さっそくその MyAsyncTask のコードをみてみましょう。

package com.keicode.android.test;

import android.app.ProgressDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnCancelListener;
import android.os.AsyncTask;
import android.util.Log;

public class MyAsyncTask 
  extends AsyncTask<String, Integer, Long> 
  implements OnCancelListener{

  final String TAG = "MyAsyncTask";
  ProgressDialog dialog;
  Context context;
  
  public MyAsyncTask(Context context){
    this.context = context;
  }
  
  @Override
  protected void onPreExecute() {
    Log.d(TAG, "onPreExecute");
    dialog = new ProgressDialog(context);
    dialog.setTitle("Please wait");
    dialog.setMessage("Loading data...");
    dialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
    dialog.setCancelable(true);
    dialog.setOnCancelListener(this);
    dialog.setMax(100);
    dialog.setProgress(0);
    dialog.show();
  }

  @Override
  protected Long doInBackground(String... params) {
    Log.d(TAG, "doInBackground - " + params[0]);
    
    try {
      for(int i=0; i<10; i++){
        if(isCancelled()){
          Log.d(TAG, "Cancelled!");
          break;
        }
        Thread.sleep(1000);
        publishProgress((i+1) * 10);
      }
    } catch (InterruptedException e) {
      Log.d(TAG, "InterruptedException in doInBackground");
    }  
    return 123L;
  }
  
  @Override
  protected void onProgressUpdate(Integer... values) {
    Log.d(TAG, "onProgressUpdate - " + values[0]);
    dialog.setProgress(values[0]);
  }
  
  @Override
  protected void onCancelled() {
    Log.d(TAG, "onCancelled");
    dialog.dismiss();
  }

  @Override
  protected void onPostExecute(Long result) {
    Log.d(TAG, "onPostExecute - " + result);
    dialog.dismiss();
  }

  @Override
  public void onCancel(DialogInterface dialog) {
    Log.d(TAG, "Dialog onCancell... calling cancel(true)");
    this.cancel(true);
  }
}

進捗のダイアログ (すなわち UI スレッドとの通信が含まれるはず)、キャンセル処理、及び肝心の非同期処理など盛りだくさんのはずなのですが、コードはこの通り非常に簡潔に済んでいます。

メソッドが呼ばれる順番とタイミング

AsyncTask クラスのメソッドは次のように呼ばれます。

  1. まずはコンストラクタ。これはいいですよね。ここではメインのアクティビティのコンテキストを受け取っています。
  2. 次に onPreExecute が最初に UI スレッドで呼び出されます。 したがってここで、 UI に関わる処理をします。ここでは ProgressDialog の設定を行っています。
  3. 次に doInBackground がワーカースレッド上で実行されます。 ここに渡されるパラメータの型は AsyncTask を extends するときのひとつめのパラメータです (AsyncTask<T, ... の T です)。

    非同期で行いたい時間のかかる処理はこの doInBackground で実行します。後で説明しますが、時々 isCancelled を呼び出してキャンセルの要求が行われていないか確認します。

    また進捗をアップデートするには、この中で publishProgress を呼ぶことで行います。
  4. doInBackground 内で publishProgress が呼ばれると、 UI スレッド上で onProgressUpdate が呼ばれます。 onProgress の引数の型は AsyncTask の extends 時のふたつめのパラメータです。
  5. doInBackground が終わると、これの戻り値をパラメータとして渡して onPostExecute が呼ばれます。 この値の型は AsyncTask を extends するときの三つめのパラメータです。

ではこのプログラムを動かして、デバッグログをみてみましょう。

AsyncTask と ProgressDialog

このとおり、確かに doInBackground には execute に渡したパラメータが渡されていること、 publishProgress に渡した値が onUpdateProgress で受け取れていること、 そして、 doInBackground の戻り値が onPostExecute に渡っていることが分かります。

doInBackground 以外は UI スレッド上で実行されるので、 UI に関わる処理が問題なくそのまま行えています。

処理のキャンセル

処理をキャンセルする場合は、 AsyncTask の cancel メソッドを呼びます。

すると isCancelled が true になるとか、今回の例のように sleep が InterruptedException となるので、 適切な終了処理をした後で doInBackground を抜けることができます。

キャンセルした状態で doInBackground を抜けると onPostExecute ではなく、 onCancelled が呼ばれます。

実行結果は次の通り。

AsyncTask と ProgressDialog

ProgressDialog がキャンセルイベントを捕まえたところで、 AsyncTask の cancel を呼び出す。すると、doInBackground は InterruptedException がおきて終了して、その後 onPostExecute ではなく onCancelled が呼ばれました。

ちなみに、 onCancelled をオーバーライドした時は super.onCancelled は呼ばないように気をつけてください。

ここまでお読みいただき、誠にありがとうございます。SNS 等でこの記事をシェアしていただけますと、大変励みになります。どうぞよろしくお願いします。

© 2024 Android 開発入門