2013年4月10日水曜日

Activity#runOnUiThread(Runnable)のススメ

たとえば、非同期処理からUIスレッドに描画したくて、こんなコードを書いたとする。

private mHandler = new Handler();  private void sayHello() {      mHandler.post(new Runnable() {          @Override          public void run() {              Toast.makeText(this, "Hello?", Toast.LENGTH_SHORT).show();          }      }  };  

Handlerにpostしたメッセージをハンドルしないのならば、Activity#runOnUiThread(Runnable)に置き換えたほうがスッキリ。

private void sayHello() {      runOnUiThread(new Runnable() {          @Override          public void run() {              Toast.makeText(this, "Hello?", Toast.LENGTH_SHORT).show();          }      }  };  

説明

APIリファレンスにActivity#runOnUiThread(Runnable)の仕様が書かれている。

Runs the specified action on the UI thread. If the current thread is the UI thread, then the action is executed immediately. If the current thread is not the UI thread, the action is posted to the event queue of the UI thread.

UIスレッドから実行した場合と、他スレッドから実行した場合の振る舞いが異なる。

  • UIスレッドから実行した場合、引数のRunnableのrun()をすぐに実行
  • 他スレッドから実行した場合、引数のRunnableをHandlerにpostし、後でLooperがrun()を実行


(おそらく)この仕様は、UIスレッドからの処理と、他スレッドからの非同期処理を別々のメソッドとして用意しなくても良いように配慮されたものではないかと。

Handle#post(Runnable)を使う場合

たとえば、こんなコードを書いたとする。

// UIスレッドからの処理ではsayHello()を使うこと!  private void sayHello() {      Toast.makeText(this, "Hello?", Toast.LENGTH_SHORT).show();  };  // 他スレッドからの非同期処理ではsayHelloForAsync()を使うこと!  private void sayHelloForAsync() {      mHandler.post(new Runnable() {          @Override          public void run() {              sayHello();          }      }  };  

そして、誰かがこんなコードを書いたとする。

@Override  private void onClick(View v) {      sayHelloForAsync(); // [1]      Toast.makeText(this, "How low?", Toast.LENGTH_SHORT).show(); // [2]  };  

実行すると、[2] "How low?" → [1] "Hello?" の順にトーストが表示される。

Handlerにpostすると、RunnableがUIスレッドの後ろのほうにキューイングされるので、UIスレッドからsayHelloForAsync()をコールすると、コール元の処理をすべて完了([2] "How low?"を表示)してから、sayHelloForAsync()を実行([1] "Hello?"を表示)する。

ただし、このように動作するからといって、意図してこのようなコードを書くべきではない。

フィールド等を更新したり参照したりする処理が含まれていると不定な動作となるのでバグの温床となるし、そもそもUIスレッドからHandlerにpostした時の順序を保証する文書はどこにもない。

Activity#runOnUiThread(Runnable)を使う場合

Activity#runOnUiThread(Runnable)では「UIスレッドから実行した場合、引数のRunnableのrun()をすぐに実行」するため、別々のメソッドを用意する必要がない。

private void sayHello() {      runOnUiThread(new Runnable() {          @Override          public void run() {              sayHello();          }      }  };  

sayHello()を使って、誰かがこんなコードを書いたとする。

@Override  private void onClick(View v) {      sayHello(); // [1]      Toast.makeText(this, "How low?", Toast.LENGTH_SHORT).show(); // [2]  };  

実行すると、[1] "Hello?" → [2] "How low?" の順にトーストが表示される。

Runnableの生成や実行をするためのコストを気にする人がいるかも知れないが、UIスレッドには全体のパフォーマンスに影響するような処理をさせちゃダメだし、Runnableオブジェクトの生成を何回も繰り返しているのならば、それは単にお行儀の悪いJavaコードを書いたことが問題だと思う。

まとめ

Handlerをpostするメソッドは、非同期処理からコールされることを前提としているはずなので、単純にActivity#runOnUiThread(Runnable)へ置き換えても問題ない。

もし、Handlerを使ってUIスレッドの実行順序に依存するようなコードが書かれていたならば、置き換えはできないが、何らかの潜在バグがあると疑ったほうがいい。

2013年4月6日土曜日

MS、Macユーザー向けにWin8ProとParallelsを25ドルで提供することを発表するもすぐ完売

MicrosoftがMacを使っているWeb開発者向けにWindows 8 Proと仮想化ソフトウェアであるParallels Desktop for Macを25ドルで提供するという「Windows QuickStart Kit for Mac Developers」を発表したそうだ(Engadget)。支払われた25ドルは全額が非営利団体に寄付されるというもので、数量限定のパッケージだったそうなのだが、発表から半日足らずで「完売」したとのこと。

提供されるのはParallels Desktop 8と、そこで動作するWindows 8が収録されたWindows 8。どのようなライセンスになっているかは不明。

なお、MacやLinux、Windowsで利用できるWindows 7+IE10やWindows XP+IE8という環境が含まれた仮想マシンはmodern.IEページから(今回の件とは別に誰でも)ダウンロードできるようになっているので、テストでIE環境が必要、という人はこちらを利用するとよいかもしれない。