プログレスダイアログの利用 [プログラミング]
Androidで、ファイルのコピーをする処理が必要になりました。
プログレスダイアログ(ProgressDialog
)には、コピーの進捗を表示します。
で、こんなコードを書いたんですよ。
// プログレスダイアログの状態を変更するHandlerクラス class ProgressHandler extends Handler { static private final int WHAT_PROGRESS = 0; static private final int WHAT_DISMISS = 1; private final ProgressDialog dialog_; public ProgressHandler(ProgressDialog dialog) { dialog_ = dialog; } @Override public void handleMessage(Message mes) { switch (mes.what) { case WHAT_PROGRESS: dialog_.setProgress(mes.arg1); break; case WHAT_DISMISS: dialog_.dismiss(); break; } } public void setProgress(int value) { sendMessage(Message.obtain(this, WHAT_PROGRESS, value, 0)); } public void dismiss() { sendMessage(Message.obtain(this, WHAT_DISMISS)); } }
// ファイルコピーを行うスレッド public class FileCopyThread extends Thread { final private ProgressHandler handler_; final private File src_file_; final private File dest_file_; volatile private boolean canceled_ = false; public FileCopyThread(ProgressHandler handler, File src_file, File dest_file) { handler_ = handler; src_file_ = src_file; dest_file_ = dest_file; } @Override public void run() { InputStream in = null; OutputStream out = null; try { in = new BufferedInputStream(new FileInputStream(src_file_)); out = new BufferedOutputStream(new FileOutputStream(dest_file_)); final int BUFF_SIZE = 1024; final byte[] buff = new byte[BUFF_SIZE]; int total = 0; int size; while ((size = in.read(buff)) != -1) { if (canceled_) return; out.write(buff, 0, size); if (canceled_) return; total += size; handler_.setProgress(total); } } catch (Exception e) { } finally { close(in); close(out); handler_.dismiss(); } } public void cancel() { canceled_ = true; } static private void close(Closeable o) { if (o != null) { try { o.close(); } catch (IOException e) { } } } }
// ファイルコピーを開始するActivityのメソッド private void copyFile(File src_file, File dest_file) { final ProgressDialog dialog = new ProgressDialog(this); dialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); dialog.setMessage("コピー中 …"); dialog.setCancelable(true); dialog.setMax((int)src_file.length()); final ProgressHandler handler = new ProgressHandler(dialog); final FileCopyThread th = new FileCopyThread(handler, src_file, dest_file); dialog.setOnCancelListener(new DialogInterface.OnCancelListener() { @Override public void onCancel(DialogInterface dialog) { th.cancel(); } }); dialog.show(); th.start(); }
- 時間のかかる処理は別のスレッドを作って、そっちで実行。
- 別スレッドの処理からUIを操作する場合は、Handlerクラスを経由してUIスレッドで実行。
というAndroidの作法 & 制限のため、FileCopyThread
とProgressHandler
を作成しています。
コピー処理自体は、FileInputStream
から 1024byte ずつ入力し、FileOutputStream
へ出力するという単純なもので、その都度プログレスダイアログの進捗を更新しています。
ファイルのコピーなら FileChannel
を使うのが効率的なんだろうけど、実はファイル以外のコピーも想定していたりするので FileInputStream
で…。
閑話休題。
これで普通に動くには動くのですが、6M byte 程度のファイルをコピーしようとした時に、それは起こりました。
プログレスダイアログのバーが、全くと言っていいほど動きません。
(別スレッドの)ファイルのコピー処理が終わってもプログレスダイアログはまだ 10% とか表示していて、ダイアログも表示されたままです。
(大分経ってからダイアログは消えました)
何が起こったのでしょう。
結論から言えば、UIスレッドがメッセージを処理し切れなかった、です。
1Kbyte ごとにプログレスダイアログの進捗更新のメッセージがUIスレッドに送られています。
ファイルは 6Mbyte あるので、約 6000 個のメッセージが発生した事になります。
このメッセージは、UIスレッドのメッセージキューに溜まって順番に処理されるのですが、次々と送られてくるメッセージを捌ききれなかった様です。
そのため、画面更新がファイルコピースレッドの処理から遅れていったのです。
この辺は端末の性能にも依るのでしょうが、自分の環境(Androidエミュレータ)では全然駄目でした。GC も何度か動いていました。
解決方法としては、プログレスダイアログ更新のメッセージ送信を間引けば良いのです。
が、どの程度間引くのが適切なのでしょう。
それこそ端末の性能に依存する話です。
そこで、『前回送信したメッセージが処理されていなければ、次のメッセージは送信しない』という方針を採用しました。
// プログレスダイアログの状態を変更するHandlerクラス class ProgressHandler extends Handler { static private final int WHAT_PROGRESS = 0; static private final int WHAT_DISMISS = 1; volatile private boolean processed_message = true; private final ProgressDialog dialog_; public ProgressHandler(ProgressDialog dialog) { dialog_ = dialog; } @Override public void handleMessage(Message mes) { switch (mes.what) { case WHAT_PROGRESS: dialog_.setProgress(mes.arg1); processed_message = true; break; case WHAT_DISMISS: dialog_.dismiss(); break; } } public void setProgress(int value) { if (processed_message) { processed_message = false; sendMessage(Message.obtain(this, WHAT_PROGRESS, value, 0)); } } public void dismiss() { sendMessage(Message.obtain(this, WHAT_DISMISS)); } }
他のソースは変更なしです。
コメント 0