2012年9月4日火曜日

Android AsyncTaskでProgressDialogを表示 その1

AsyncTaskは別スレッドでのバックグラウンド処理とバックグラウンド前後にUIスレッドの操作を行う抽象クラスです。
AsyncTaskを継承するとき実行時引数(Param)、進捗単位(Progress)、処理結果(Result)で扱う[型]を指定します。
実行時引数(Param)はdoInBackgroundメソッドの引数の型になり、
処理結果(Result)はdoInBackgroundメソッドの戻り値の型になります。
進捗単位(Progress)はonProgressUpdateメソッドの引数の型になります。
また、doInBackgroundメソッドの戻り値を引数にonPostExecuteメソッドが呼ばれるので、このメソッドの引数の型は処理結果(Result)の型と同じになります。
public class ProgressTask extends AsyncTask<String, Integer, Boolean>{
    protected Boolean doInBackground(String... params) {}
    protected void onPostExecute(Boolean result) {
    protected void onProgressUpdate(Integer... values) {}
}
型を何も指定しない場合は「Void」を指定します。
public class ProgressTask extends AsyncTask<Void, Void, Void>{
    protected Void doInBackground(Void... params) {}
    protected void onProgressUpdate(Void... values) {}
    protected void onPostExecute(Void result){}
}

AsyncTaskには以下のメソッドがあります。
  • onPreExecute()・・・最初にUIスレッドで呼び出されます。UIに関わる処理をします。
  • doInBackground()・・・ワーカースレッド上で実行されます。ここでバックグラウンドで行う時間のかかる処理をします。
  • onProgressUpdate()・・・doInBackground内でpublishProgressメソッドが呼ばれると、 UIスレッドでこのメソッドが呼び出されます。
  • onPostExecute()・・・doInBackgroundメソッドの処理が終了すると、 UIスレッドで呼び出されます。
  • onCancelled()・・・タスクがキャンセルされると、 UIスレッドで呼び出されます。

AsyncTaskでProgressDialogを表示する際のキモは、以下の2点です。
  1. プログレスダイアログを表示している時にディスプレイの縦横を変更した時の処理
  2. プログレスダイアログをキャンセルした時の処理
とりあえずコードです。
AsyncTaskを継承したProgressTaskクラス
package com.example.helloandroid.service;

import android.app.ProgressDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.os.AsyncTask;
import android.os.SystemClock;


public class ProgressTask extends AsyncTask<:Void,Integer,Boolean>{
    private Context mContext;
    private ProgressDialog mProgressDialog;
    private Boolean isShowProgress;

    /**
     * コンストラクタ
     * @param context
     */
    public ProgressTask(Context context){
        mContext = context;
    }

    /**
     * getIsShowProgress
     * プログレスダイアログが表示中かどうかを返します。
     * @return
     */
    public Boolean getIsShowProgress() {
        return isShowProgress;
    }

    /**
     * onPreExecute
     * 最初にUIスレッドで呼び出されます。
     * UIに関わる処理をします。
     */
    @Override
    protected void onPreExecute() {
        super.onPreExecute();
        isShowProgress = true;
        showDialog();
    }

    /**
     * doInBackground
     * ワーカースレッド上で実行されます。
     * このメソッドに渡されるパラメータの型はAsyncTaskの一つ目のパラメータです。
     * このメソッドの戻り値は AsyncTaskの三つ目のパラメータです。
     * @return 
     */
    @Override
    protected Boolean doInBackground(Void... params) {
        for(int i=0; i<:10; i++){
            SystemClock.sleep(1000);
            // キャンセルが押された場合                
            if (isCancelled()) {   
                return false;                
            }
            publishProgress((i+1) * 10);
        }
        return true;
    }

    /**
     * onProgressUpdate
     * doInBackground内でpublishProgressメソッドが呼ばれると、 UIスレッド上でこのメソッドが呼ばれます。 
     * このメソッドの引数の型はAsyncTaskの二つ目のパラメータです。 
     * @param values
     */
    @Override
    protected void onProgressUpdate(Integer... values) {
        super.onProgressUpdate(values);
        mProgressDialog.setProgress(values[0]);  
    }

    /**
     * onPostExecute
     * doInBackground が終わるとそのメソッドの戻り値をパラメータとして渡して onPostExecute が呼ばれます。
     * このパラメータの型は AsyncTask を extends するときの三つめのパラメータです。
     * バックグラウンド処理が終了し、メインスレッドに反映させる処理をここに書きます。
     * @param result
     */
    @Override
    protected void onPostExecute(Boolean result) {
        super.onPostExecute(result);
        dismissDialog();
        isShowProgress = false;
    }

    /**
     * onCancelled
     * cancelメソッドが呼ばれるとonCancelledメソッドが呼ばれます。
     */
    @Override
    protected void onCancelled() {
        super.onCancelled();
        dismissDialog();
        isShowProgress = false;
    }

    /**
     * showDialog
     * プログレスダイアログを表示します
     */
    public void showDialog() { 
        mProgressDialog = new ProgressDialog(mContext);
        mProgressDialog.setMessage("データ読み込み中...");
        mProgressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
        mProgressDialog.setIndeterminate(false);
        mProgressDialog.setMax(100);
        mProgressDialog.incrementProgressBy(0);
        mProgressDialog.setCancelable(true);
        mProgressDialog.setButton("キャンセル", new DialogInterface.OnClickListener() {
            public void onClick(DialogInterface dialog, int which) {
                cancel(true); // 非同期処理をキャンセルする
            } 
        });
        mProgressDialog.show();
    }

    /**
     * dismissDialog
     * ダイアログを終了する。
     */
    public void dismissDialog() {
        mProgressDialog.dismiss();
        mProgressDialog = null;
    }

}
呼び出しもとのMainActivityクラス
package com.example.helloandroid.activity;

import android.os.Bundle;
import android.app.Activity;
import android.view.Menu;
import android.view.View;

import com.example.helloandroid.R;
import com.example.helloandroid.service.ProgressTask;

public class MainActivity extends Activity {
    //Activityが破棄されても、オブジェクトを保持できるようにstaticで宣言する。
    private static ProgressTask mTask; 

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);        
        //プログレスダイアログを再表示する。
        if (mTask != null && mTask.getIsShowProgress()){
            mTask.showDialog();
        }
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.activity_main, menu);
        return true;
    } 

    @Override
    protected void onPause() {
        //プログレスダイアログを閉じる。
        if (mTask != null && mTask.getIsShowProgress()) {
            mTask.dismissDialog(); 
        }
        super.onPause();
    }

    public void button1Click(View view) {
        //時間のかかる処理を別スレッドで実行
        mTask = new ProgressTask(this);
        mTask.execute();
    }  
}
ダイアログ表示中にディスプレイの縦横を切り替えるとダイアログが消えてしまいます。
これはディスプレイの縦横を変更すると呼び出し元のアクティビティが破棄され、新しい向きのActivityが生成されます。 そのためダイアログも消えてしまうようです。
またAsyncTaskの処理が終了した時点でIllegalArgumentExceptionが発生してしまいます。
これはダイアログのdismissメソッドを呼び出す時に、ダイアログを表示したアクティビティが破棄されているのが原因のようです。
詳しくはこちらに書かれています。
Tech Racho「Android: ダイアログを表示して縦横が変わるとdismissでエラー」
kasa0の部屋「ProgressDialogとAsyncTaskの甘い罠」
対策は
ProgressTaskクラスでダイアログ表示中かどうかの状態を管理します。
MainActivityクラスでProgressTaskオブジェクトをstatic変数で保持します。
MainActivityクラスのonPauseメソッドでプログレスダイアログを閉じます。
MainActivityクラスのonCreateメソッドでプログレスダイアログを表示します。

キャンセル時の処理について
キャンセルボタンクリック時にProgressTask#cancel(true)を呼びます。
doInBackgroundメソッドではキャンセルされたかどうかを見て、処理を行います。
キャンセルされるとonPostExecute()メソッドは呼ばれない為、onCancelledメソッドで終了処理を行います。

0 件のコメント: