SSブログ

プログレスダイアログの利用 [プログラミング]

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の作法 & 制限のため、FileCopyThreadProgressHandlerを作成しています。

コピー処理自体は、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));
    }
}

他のソースは変更なしです。


タグ:Android
nice!(0)  コメント(0)  トラックバック(0) 

nice! 0

コメント 0

コメントを書く

お名前:
URL:
コメント:
画像認証:
下の画像に表示されている文字を入力してください。

トラックバック 0

この広告は前回の更新から一定期間経過したブログに表示されています。更新すると自動で解除されます。