ボタンをクリックすると、次のスクリーンショットのようにプログレスバー付きの ProgressDialog が表示さるプログラムのソースコードです。

非同期処理であるAsyncTask を利用していますが、ネットのサンプルだと
などでエラーが起きたり好ましくない動作をしていたので、その辺りを修正してみました。
スポンサードリンク
// Progress Dialog を表示
public void showDialog() {
mDialog = new ProgressDialog(mContext);
mDialog.setTitle("Please wait");
mDialog.setMessage("Downloading data...");
mDialog.setCancelable(true);
mDialog.setCanceledOnTouchOutside(false);
mDialog.setIndeterminate(false);
mDialog.setProgressNumberFormat("%1$s / %2$s bytes");
mDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
mDialog.setMax(0);
mDialog.setProgress(0);
mDialog.show();
}
// Progress Dialog を消す
private void dismissDialog() {
mDialog.dismiss();
mDialog = null;
}
ProgressDialogの代表的なメソッドは、次のとおりです。
| メソッド | 説明 |
|---|---|
| setTitle | タイトルを設定する |
| setMessage | メッセージを設定する |
| setProgressNumberFormat | 進捗表示のフォーマットを設定する |
| setProgressStyle | スタイルを設定する(STYLE_HORIZONTAL:水平スタイル、STYLE_SPINNER:円スタイル) |
| setCancelable | プログレスダイアログのキャンセルが可能かどうかを設定 |
| setCanceledOnTouchOutside | ダイアログの外部をタッチしてダイアログを閉じる(true)/閉じない(false)を設定 |
| setIndeterminate | プログレスダイアログの確定(false)/不確定(true)を設定 |
| setProgress | 開始値を設定する |
| setMax | 最大値を設定する |
| setButton | プログレスダイアログにボダンを設置する |
| show | プログレスダイアログを表示する |
| dismiss | プログレスダイアログを閉じる |
処理をキャンセルする場合は、 AsyncTask の cancel メソッドを呼びます。
すると isCancelled が true になるとか、今回の例のように sleep が InterruptedException となるので、 適切な終了処理をした後で doInBackground を抜けることができます。
キャンセルした状態で doInBackground を抜けると onPostExecute ではなく、 onCancelled が呼ばれます。
| メソッド名 | 内容 |
|---|---|
| onPreExecute() | 事前準備の処理 |
| doInBackground(Params...) | バックグラウンドで行う処理 |
| onProgressUpdate(Progress...) | 進捗状況をUIスレッドで表示する処理 |
| onPostExecute(Result) | バックグラウンド処理が完了し、UIスレッドに反映する処理 |
ProgressDialog がキャンセルイベントを捕まえたところで、 AsyncTask の cancel を呼び出します。
New を行わないで、インスタンスを使いまわすと 次のような IllegalStateException となります。
Cannot execute task: the task has already been executed (a task can be executed only once)
タスクは1回しか実行できないので、毎回インスタンスを生成する必要があります。
AsyncTaskの処理が終了した時点でIllegalArgumentExceptionが発生してしまいます。
これはダイアログの dismissメソッドを呼び出す時に、ダイアログを表示したアクティビティが破棄されているのが原因です。
E/WindowManager(16629): android.view.WindowLeaked: Activity org.example.demomode.DemoMode has leaked window com.android.internal.policy.impl.PhoneWindow$DecorView{41c43ef0 V.E..... R.....I. 0,0-310,215} that was originally added here
E/WindowManager(16629): at android.view.ViewRootImpl.(ViewRootImpl.java:354)
E/WindowManager(16629): at android.view.WindowManagerGlobal.addView(WindowManagerGlobal.java:216)
E/WindowManager(16629): at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:69)
E/WindowManager(16629): at android.app.Dialog.show(Dialog.java:281)
E/WindowManager(16629): at org.example.demomode.AsyncDemoTask.showDialog(AsyncDemoTask.java:43)
E/WindowManager(16629): at org.example.demomode.AsyncDemoTask.onPreExecute(AsyncDemoTask.java:50)
E/WindowManager(16629): at android.os.AsyncTask.executeOnExecutor(AsyncTask.java:586)
E/WindowManager(16629): at android.os.AsyncTask.execute(AsyncTask.java:534)
E/WindowManager(16629): at org.example.demomode.DemoMode$1.onClick(DemoMode.java:37)
E/WindowManager(16629): at android.view.View.performClick(View.java:4202)
E/WindowManager(16629): at android.view.View$PerformClick.run(View.java:17340)
対策方法は次のようになります。
画面の回転時にはActivityが再起動(onDestroy~onCreate)します。
上記対応により、ダイアログ表示したままでActivityが再起動しても、Activityと一緒に自動的にダイアログも再表示されます。
ホームボタンを押してアプリがバックグラウンドに回る時や、他のアプリに遷移した時は処理を終了することなく、処理を継続させます。
それには「onUserLeaveHint」を呼び出します。
@Override
public void onUserLeaveHint() {
if (mTask != null && mTask.isInProcess()) {
Log.v("", "dismissDialog called");
mTask.dismissDialog();
}
// トーストを表示する
Toast.makeText(getApplicationContext(), "Good bye" , Toast.LENGTH_SHORT).show();
}
動作確認のためにトースト表示も行なっています。
サンプルソースコードを載せておきます。
package org.example.demomode;
import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.view.View;
import android.view.Window;
import android.view.KeyEvent;
import android.util.Log;
import android.widget.Button;
import android.widget.Toast;
public class DemoMode extends Activity {
// AsyncDemoTaskオブジェクトをstatic変数で保持
private static AsyncDemoTask mTask = null;
// 存在すれば onCreate, onStart, onResumeの順番で呼ばれる
@Override
public void onCreate(Bundle savedInstanceState) {
Log.v("", "onCreate called");
super.onCreate(savedInstanceState);
// ウィンドウタイトルバーを非表示にする
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.main);
findViewById(R.id.button_id).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.v("", "mTask.execute called");
if (mTask != null) {
mTask.cancel(true);
}
// プログレスダイアログを表示
mTask = new AsyncDemoTask (DemoMode.this);
mTask.execute();
}
});
}
// 存在すれば onCreate, onStart, onResumeの順番で呼ばれる
@Override
public void onResume(){
super.onResume();
Log.v("", "onResume called");
// プログレスダイアログの表示開始
if (mTask != null && mTask.isInProcess()) {
Log.v("", "showDialog called");
// プログレスダイアログの再表示
mTask.showDialog();
}
}
// 戻るボタンが押された
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if(keyCode == KeyEvent.KEYCODE_BACK && mTask != null) {
Log.v("", "keyCode == KeyEvent.KEYCODE_BACK");
// AsyncTaskのインスタンは使い回しできないので破棄
mTask.cancel(true);
mTask = null;
}
return super.onKeyDown(keyCode, event);
}
//ホームボタン押、他のアプリが起動した時に呼ばれる
@Override
public void onUserLeaveHint() {
if (mTask != null) {
Log.v("", "dismissDialog called");
// プログレスダイアログを閉じる(2重表示防止)
mTask.dismissDialog();
// 処理は継続するのでインスタンスの破棄はしない
// mTask = null;
}
// トーストを表示する
Toast.makeText(this, "Good bye" , Toast.LENGTH_SHORT).show();
}
}
AsyncTask を使ったProgressDialog表示のソースコードを紹介します。
package org.example.demomode; import android.app.ProgressDialog; import android.content.Context; import android.content.DialogInterface; import android.os.SystemClock; import android.os.AsyncTask; import android.util.Log; import android.widget.Toast; public class AsyncDemoTask extends AsyncTask{ private ProgressDialog mDialog = null; private Context mContext = null; // onPreExecute ~ onPostExecute までの判別フラグ private boolean isInProgress = false; // インスタンス生成 public AsyncDemoTask(Context context) { mContext = context; } // プログレスダイアログを閉じる public void dismissDialog() { if (mDialog != null) { mDialog.dismiss(); } mDialog = null; } // Progress Dialog を表示 public void showDialog() { mDialog = new ProgressDialog(mContext); mDialog.setTitle("Please wait"); mDialog.setMessage("Downloading data..."); mDialog.setCancelable(true); mDialog.setIndeterminate(false); // ダイアログの外部をタッチしてもダイアログを閉じない mDialog.setCanceledOnTouchOutside(false); mDialog.setProgressNumberFormat("%1$s / %2$s bytes"); mDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); mDialog.setMax(0); mDialog.setProgress(0); mDialog.show(); } // 事前準備の処理 @Override public void onPreExecute() { isInProgress = true; showDialog(); } // バックグラウンドで行う処理 @Override public Integer doInBackground(Integer...ARGS) { for (int i=0; i<10; i++) { SystemClock.sleep(1000); // キャンセルが押された場合 if (isCancelled() ) { return 0; } publishProgress((i+1) * 10); } return 1; } // 進捗状況をUIスレッドで表示する処理 @Override protected void onProgressUpdate(Integer... values) { super.onProgressUpdate(values); if (null != mDialog ) { mDialog.setProgress(values[0]); } } // キャンセル処理 @Override public void onCancelled(Integer result) { Log.v("", "onCancel() called"); if (mDialog != null && mDialog.isShowing() ) { dismissDialog(); } super.onCancelled(); } // バックグラウンド処理が完了し、UIスレッドに反映する処理 @Override public void onPostExecute(Integer result) { Log.v("", "onPostExecute() called"); // ProgressDialog の削除 if (mDialog != null) { dismissDialog(); } // AsyncTaskの終了 isInProgress = false; Toast.makeText(mContext, "Download Finished!" , Toast.LENGTH_SHORT).show(); } // public synchronized boolean isInProcess() { return isInProgress; } }
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/button_id"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:text="@string/button_id"
/>
</LinearLayout>
スポンサードリンク