Android: ダイアログを表示して縦横が変わるとdismissでエラー

伊藤です。

最近Androidアプリケーションの開発に関わっているのですが、
時間のかかる処理を行っているときにProgressDialogでクルクルを出して進捗や、時間がかかっていることを知らせるのはよくある方法かと思いますが、
今回記事にしようと思ったのは普通に実装するとProgressDialogが表示されているときに、画面の縦横を切り替えると、
ダイアログを消せないという問題に遭遇しました。

問題のコードは以下のようなものです。
少し長いですが、おつきあいください。

// 時間のかかる処理の前後にProgressDialogをshow/dismissするユーティリティクラス
public abstract class ShowProgressTask<Params, Progress, Result> extends
        AsyncTask<Params, Progress, Result> {

    private ProgressDialog mProgressDialog;

    public ShowProgressTask(ProgressDialog progressDialog) {
        super();
        mProgressDialog = progressDialog;
    }

    @Override
    protected void onPreExecute() {
        showProgressDialog();
    }

    @Override
    protected void onPostExecute(Result result) {
        dismisProgressDialog();
    }

    protected void showProgressDialog() { mProgressDialog.show(); }

    protected void dismisProgressDialog() {
        if (mProgressDialog != null && mProgressDialog.isShowing()) {
            mProgressDialog.dismiss();
        }
    }

    protected ProgressDialog getProgressDialog() { return mProgressDialog; }

}

class HogeActivity extends Activity {

	// Fileオブジェクトを引数にとり、処理結果をMyDataを返すような処理
	public class MyTask extends ShowProgressTask<File, Void, MyData> {
		public MyTask(ProgressDialog progressDialog) { super(progressDialog); }
		public MyData doBackInGround(File... params) {
			// 時間のかかる処理, MyDataを返す
		}
		public void onPostExecute(MyData result) {
			super.onPostExecute(result);
			// doBackInGroundがおわったあとに呼び出される
		}
	}

	// MyTaskをMyTask myTask = new MyTask(new ProgressDialog(this));
	// のように生成してmyTask.execute(fileObject);のように呼び出す処理

}

めでたくHogeActivityがうまくうごき、時間のかかる処理をプログレスダイアログ表示するためにMyTaskwを呼び出し、
いいかんじにプログレスダイアログのクルクルが回ってくれるとします。
まわっているときにAndroid端末を傾けて縦横を変えてみてください。
マニフェストファイルでorientationが固定されていないActivityであれば、このタイミングで現在フォアグラウンドにあるアクティビティのonPauseがよばれ、
このアクティビティは破棄されます。その後、新しい向きのActivityが生成されます。
MyTaskの処理はAsyncTaskを継承していますので、Activityのライフサイクルとは独立して並行動作しているので、
いずれ終了するとonPostExecuteのdismissが呼ばれ、ダイアログを閉じる処理が実行されますが、このタイミングで以下のURLで報告されているような
Activity名 has leaked window
といった例外が発生します。
http://groups.google.co.jp/group/android-developers/browse_thread/thread/5ec21fac2a7f52d8?pli=1

これはどうやらMyTask(ShowProgressDialog)が保持しているProgressDialog、が保持しているActivityのインスタンスが不正な参照というカンジのようでしたので、
以下のような変更を行うことで、縦横を切り替えて、AndroidがActivityオブジェクトをとっかえひっかえしても処理中のプログレスダイアログを持続的に表示させることが可能になりました。

  • MyTaskクラスはstaticクラスに
  • MyTaskを格納する変数はstatic変数に
  • ShowProgressDialogで実行中かどうかの状態を管理する
  • Activity#onPause()でMyTaskのdialogをdismissする
  • Activity#onCreate()でMyTaskオブジェクトがまだタスク処理中であれば新しいプログレスダイアログを設定し, 表示させる

最後に以上を適用したバージョンのコードを貼付けておきます。

// 時間のかかる処理の前後にProgressDialogをshow/dismissするユーティリティクラス
public abstract class ShowProgressTask<Params, Progress, Result> extends
        AsyncTask<Params, Progress, Result> {

    private ProgressDialog mProgressDialog;
    private boolean isInProcess; // 新しく追加: 状態を管理している変数(trueなら処理中)

    public ShowProgressTask(ProgressDialog progressDialog) {
        super();
        mProgressDialog = progressDialog;
    }

    // 新しく追加: Activity#onPauseで呼んであげる
    public void onActivityPause() {
    	dismisProgressDialog();
    }

    // 新しく追加: Activity#onCreateで, 処理中であることを確認した上で呼んであげる
    public void onActivityCreate(ProgressDialog progressDialog) {
    	mProgressDialog = progressDialog;
    	showProgressDialog();
    }

    // 新しく追加: 処理中かどうかを調べるメソッド
    public synchronized boolean isInProcess() { return isInProcess; }

    @Override
    protected void onPreExecute() {
    	isInProcess = true; // 新しく追加: 処理開始
        showProgressDialog();
    }

    @Override
    protected void onPostExecute(Result result) {
        dismisProgressDialog();
        isInProcess = false; // 新しく追加: 処理おわり
    }

    protected void showProgressDialog() { mProgressDialog.show(); }

    protected void dismisProgressDialog() {
        if (mProgressDialog != null && mProgressDialog.isShowing()) {
            mProgressDialog.dismiss();
        }
    }

    protected ProgressDialog getProgressDialog() { return mProgressDialog; }

}

class HogeActivity extends Activity {

	public static MyTask myTask;

	// Fileオブジェクトを引数にとり、処理結果をMyDataをかえすような処理
	public static class MyTask extends ShowProgressTask<File, Void, MyData> {
		public MyTask(ProgressDialog progressDialog) { super(progressDialog); }
		public MyData doBackInGround(File... params) {
			// 時間のかかる処理, MyDataを返す
		}
		public void onPostExecute(MyData result) {
			super.onPostExecute(result);
			// doBackInGroundがおわったあとに呼び出される
		}
	}

	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);

		// 新しく追加
		if (myTask != null && myTask.isInProcess()) {
			myTask.onActivityCreate(new ProgressDialog(this)); // 新しいアクティビティへの参照を持つダイアログを設定してあげる
		}
	}

	@Override
	public void onPause() {
		super.onPause();

		// 新しく追加
		if (myTask != null && myTask.isInProcess()) {
			myTask.onActivityPause(); // アクティビティが消える前にダイアログを終了させる
		}
	}

	// MyTaskをMyTask myTask = new MyTask(new ProgressDialog(this));
	// のように生成してmyTask.execute(fileObject);のように呼び出す処理

}

Have a nice hacking day!

デザインも頼めるシステム開発会社をお探しならBPS株式会社までどうぞ 開発エンジニア積極採用中です! Ruby on Rails の開発なら実績豊富なBPS

この記事の著者

渡辺 正毅

1984年生。サンフランシスコ育ち。大学から憧れの日本に留学し、そのまま移住。2006年慶應大学SFC卒。2007年BPS株式会社設立。いい国ですよね。もっとよくしたい。好きになってくれる人を増やしたい。

渡辺 正毅の書いた記事

お知らせ
社名変更のお知らせ

2019年10月01日

夏のTechRachoフェア2019

週刊Railsウォッチ

インフラ

ActiveSupport探訪シリーズ