Android 非同期処理をAsyncTaskを使って行い進捗をProgressDialogに表示する

何らかの重い処理を、バックグラウンドで走らせつつ、進捗をプログレスダイアログに明示させる。

以下の例のように、辞書データをアプリケーションの初回起動時にロードしつつ、進捗を適宜プログレスダイアログに表示させるということをしたい。

progress_dialog01

表題の通り、非同期処理を実施しつつ、プログレスダイアログに進捗を表示させるための、非同期処理を行うには、AsyncTask クラスを使うのが吉。

Google Developer Day 2010 でも紹介されていた(下の動画の11分頃)が、こいつを使うと、要所要所UIスレッドを使うことができる。

Android 含め、ほとんどのGUI フレームワークでは、UIスレッド(イベントディスパッチスレッド)がイベントキューにたまったイベントの配信をシングルスレッドで行っているため、UIの操作はUIスレッドから行う必要がある。なので、別のスレッドをつかって非同期処理を行い進捗や結果表示のためにUIを更新するためには、何らかの方法で、UIスレッドからUIの更新を行う必要がある。

例えば、Runable を実装したクラスを別スレッド非同期に動かす場合は、Handler クラス(「AndroidのHandlerとは何か?」にわかりやすい説明あり)を使用する。UIを操作する処理を、Handler に記述しておき、非同期処理を行っているスレッドから、Handlerにメッセージを投げると、HandlerがUIスレッドのイベントキューにイベントを登録するという仕組みで、別スレッドからUIを操作することができる。

上記の方法だと、若干コードが煩雑になってしまうところ、AsyncTask クラスを使うと、かなり簡潔に非同期処理とUIの更新を両立させることができる。

AsyncTask クラスを継承し、onPreExecute()、onProgressUpdate()、onPostExecute() メソッドをオーバーライドする。これらのメソッドは順に、事前準備、処理中、処理後に呼び出される。そして、これらのメソッドの中からは、UIを操作することができる。

非同期で行うべき、主な処理は、doInBackground() に記述するが、このメソッドからはUIを触ることはできない(操作すれば例外となる)。しかしながら、進捗をUIに表示させるなど、UIの状態を更新するために、publichProgress() を呼び出すことで、onProgressUpdate() が呼び出され、前述のように、このメソッドからはUIを更新することができる。

実際に、プログレスダイアログを表示する処理は、以下。ほとんどわかりにくいコードなしに、非常に簡潔にに実現できる。

class Hoge extends AsyncTask<Void, Integer, Integer>

の下線の部分は、それぞれ、doInBackground()、onProgressUpdate()、onPostExecute() が呼び出されたときの引数の型を設定する。

AsyncTask.execute() に渡した値が、doInBackground()に、publishProgress()に渡した値がonProgressUpdate() に、doInBackground()でreturn した値が、onPostExecute()にそれぞれ渡ってくるという仕組みになっていて、それぞれ任意の型を設定することができる。

/*
 * 非同期で辞書データを取り込むクラス
 */
public class LoadDictionaryTask extends AsyncTask<Void, Integer, Integer> {
    private Context context;
    private ProgressprogressDialog progressDialog = null;

    public LoadDictionaryTask(Context context) {
        this.context = context;
    }

    @Override
    protected Integer doInBackground(Void... params) {
        // このメソッドから、UIは操作できない
        int progressCnt = 0;
        while (・・・) {

            //
            // 何らかの繰り返し処理
            //

            publishProgress(Integer.valueOf(progressCnt++));
        }
        return Integer.valueOf(progressCnt);
    }

    @Override
    protected void onPreExecute() {
        // プログレスダイアログの準備と表示
        progressDialog = new ProgressprogressDialog(this.context);
        progressDialog.setTitle(R.string.lbl_import_dic);
        progressDialog.setIndeterminate(false);
        progressDialog.setProgressStyle(ProgressprogressDialog.STYLE_HORIZONTAL);
        progressDialog.setMax(this.context.getResources().getInteger(R.integer.wordcount));
        progressDialog.incrementProgressBy(0);
        progressDialog.setCancelable(false);
        progressDialog.show();
    }

    @Override
    protected void onProgressUpdate(Integer... values) {
        // 進捗
        progressDialog.setProgress(values[0].intValue());
    }

    @Override
    protected void onPostExecute(Integer result) {
        // 終了
        progressDialog.dismiss();
    }
}

// 以下の様に呼び出す
public void loadDictionary() {
    (new LoadDictionaryTask(this.context)).execute();
}

いじょ。