Androidに美人さん(ryにプログレスバーを付けようとしたら意外に重労働になってしまった件について

Androidに美人さん(ryがGridView対応した時点でサムネイルを先に読むようになったので起動時間が随分とかかるようになりました。


対応として起動時のRSSフィード読み込み、RSS解析、サムネイルダウンロードまでの間プログレスバーを表示しようと思いました。そうしたらこれが意外にはまり道だったのです。


結論をまず先に書いておきます。公式APIドキュメントのProgressBarをご覧下さい。こちらにはサンプルコードが書いてあります。
http://code.google.com/android/reference/android/widget/ProgressBar.html
また私のソースは以下のURLにて参照できます。
http://code.google.com/p/feedimageviewer/source/browse/trunk/4uViewerV2/src/minghai/practice/FeedImageViewer.java


私は最初に公式ドキュメントのDisplaying a Progress Barから始めてしまいました。
http://code.google.com/android/kb/commontasks.html#progressbar


ここを読むとスレッドのタイミングの話題がないのです。
また上のProgressBarにも触れられておりません!
最初はここに書かれていることをonCreateに全部書いていました。
http://code.google.com/p/feedimageviewer/source/browse/branches/ThumbnailScroll/4uViewerV2/src/minghai/practice/FeedImageViewer.java


私もこれを実行してすぐに気づいたのですが、画面の更新はonCreateを実行している間には行われません。つまりProgressDialogをonCreateの中だけで表示し、更新し、消去を行うとonCreateが終わってから何も表示されずコードが無駄になります。


ここからがはまり道でした。
最初はライフサイクルを考えてonStartでやれば良いのかと思いました。しかしonStartの中で同じようにループを与えても画面の更新はonStartが終わってから行われるので同じようにうまく動きません。


使っているものが悪いのかと思い色々とクラスを変えてみましたが当たり前ですがそれではうまく行くはずがありません。あーだこーだとやっているうちに時間ばかりが過ぎていきかなり煮詰まっていました。


1つの突破点となったのが実は上と同じCommon Tasksの中にあった"Handling Expensive Operations in the UI Thread"に気づいたことでした。
http://code.google.com/android/kb/commontasks.html#threading

私は検索だよりでまともにドキュメントを読まないでやっていますのでこういう同じ文書で読んでいないとか平気でやってしまいます。ドキュメント読むのは大切です。


さて上の記事の中にはUIスレッドの中では重い仕事をしちゃダメなことと、そのためにHandlerを使う方法が載っています。
Handlerの中身はソースが公開されていないのでアレですが、Runnableをいっぱい作ることからなんとなく想像しています。早く読みたいです。

Handlerを作る位置はThreadの外です。HandlerとRunnableは使い回しできます。また私は最初に勘違いしてしまったのですがHandlerを使う度にThreadを作る必要はありません。最初は更新するたびにThreadを作ってHandlerを作っていたのですが、そうする必要はないです。

そんなこんなでできたのが下のソースです。
onCreateで始まって、loadThumbnailに飛んでThumbnailLoaderを作成し、loadとcheckをサムネイルの数繰り返します。最後のサムネイルのときだけRunnableを変更することによりupdateUIに抜けます。クラスにする必要はないはずですけど、状態遷移を考えるとすぐにクラスにする癖ですので気にしないで下さい。
Handlerを使うとかなり普通のJavaと違った形だと思いますがどう思われますでしょうか?*1

    final Handler h = new Handler();
    mp = ProgressDialog.show(FeedImageViewer.this, "Loading...", "START");
    mp.setIndeterminate(false);
    // Fire off a thread to do some work that we shouldn't do directly in the UI thread
    new Thread() {
       public void run() {
         rch.parse();

         h.post(new Runnable() {
           public void run() {
             loadThumbnails();
           }
         });
       } 
    }.start(); 
  }
  
  private class ThumbnailLoader {
    private Handler mHandler = new Handler();
    private Runnable mRun = new Runnable() {
      public void run() {
        check();
      }
    };

    private int mcount = 0;
    private int len = rch.size();
    
    void load() {
      int percent = mcount * 100 / len;
      mp.setMessage(Integer.toString(percent) + "%");
      mp.setProgress(percent * 100);

      rch.getThumbnail(mcount);
      mHandler.post(mRun);
    }
    
    void check() {
      if (++mcount == len - 1) {
        // Only last time changes mRun to exit
        mRun = new Runnable() {
          public void run() {
            updateUI();
          }
        };
      }
      load();
    }
  }
  
  private void loadThumbnails() {
    new ThumbnailLoader().load();
  }
  
  private void updateUI() {
    mp.dismiss();

おまけ

API DEMOのプログラムはボタンを押すとProgressBarを変更するタイプのため実は参考になりません。ボタンを利用すれば必然的にボタンが押されるまで画面更新Threadに実行が移ります。Handlerがいらないわけです。


上のソースはProgressBarのAPIドキュメントを見る前に書きました。SVNの履歴に恥が残ってます。


大量にあるAPI DEMOのなかからProgressBar(ProgressDialog)を探すのは実は大変だったりするのですが、Appの中のDialogとViewのなかのProgressBarが該当します。そこまでわかってもAPI Demoの大量のソースのどれがそれに相当するのか探すのはEclipseのSearchなどを使わないと難しく感じることでしょう。Layoutも探さなければいけないですし、IDからLayoutを探すのもまた面倒だったりします。ここでシステムの中だったりするとさらにさらに大変になるのです。例えばシステムのR.idとか使われたりするとかなり難しくなります。こうなるとJADってなんJAD?とか先日お知らせしたWBXMLの解析結果が必要になったりします。


早くソース公開してほしいです。
こういうこと書いていると怒られそうですが、実際には出ないのはわかってます。(^^;
先日Google Developers Dayにてジェイソンさんにお話聞いてきましたけど、Googleの社員さんは皆良く鍛えられていまして今後の予定とかは公表されている以上のことは教えてもらえないですね。当たり前ですが。


Google App EngineのQAにて、ぜひRuby採用を!との熱い意見に「フィードバックをありがとう」で返されたのには笑いました。自分が発表者だったらどうなるかな。

ProgressBarの不思議な世界

styleってなんですか、とかどうしてこんなにありますかとか。

プログレスバーといいつつ、バーじゃないじゃんという意見が聞こえそうですが、ProgressDialogのデフォルトビューがCircleのため、自分でsetContentViewしないとバーになりません。


ProgressDialogのViewをsetContentViewして使っている例ですが、API DemoのAlertDialogSamplesにてやっていました。ただInflateって何?の状態です。なんか始めたら非常にはまり道のような予感がしてやっていません。


ProgressBarをLayoutに組み込んだデモはあるのですが、それには得体の知れないシステムIDがスタイルの指定に用いられています。
ProgressDialogのPublicなコンストラクタにはこのIDを指定できるものがあるのですが、ID一覧はドキュメントになかったりします。たぶんProgressDialogは未完成なのだと想像しています。

やっぱ関数オブジェクトが欲しいですね

Java7で大喧嘩してますけど欲しいですね。Joshua Blockのプレゼンを見てからガクブルなんですけど、Gosling支持の案で決まってしまうのでしょうか。JDK5でのGenerics大失敗を反省してとにかくシンプルで簡単であまり皆が引かないものにしてほしいものです。

AndroidのオフィシャルコードではGenericsEclipseの自動生成のためか良く使ってらっしゃいますが、警告は無視の方向のようですね。賢明でしょうね。

*1:今見るとかなり無駄が多い気がしますが直す気力が失せてきてます