2011年11月14日月曜日

Activity#runOnUiThread(Runnable)の実装を読む&注意点

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク
実はrunOnUiThreadを最近まで知らなくていつもアホみたいにHandler#postしていてなんだか悔しかったのでActivity#runOnUiThread(Runnable)の実装を読んでみました。

Activity#runOnUiThread


Activity#runOnUiThreadの実装がこちら。

public final void runOnUiThread(Runnable action) {
    if (Thread.currentThread() != mUiThread) {
        mHandler.post(action);
    } else {
        action.run();
    }
}

内部的にHandler#postしていますね。
ここでちょっと気になるのが
Thread.currentThread() != mUiThread
この比較です。runOnUiThreadを実行しているスレッドがUIスレッドかどうかチェックしています。
そしてUIスレッドである場合は
action.run();
指定したRunnableをrunOnUiThreadの中で即実行しています。つまりrunOnUiThreadはRunnableを実行する為にブロッキングされるという事です。

どんな時問題になるか


runOnUiThreadをHandler#postと同じモノだと考えて利用しているとハマる可能性があります。
まぁ多分大丈夫と思いますが以下のhello2()様な実行順に依存性があるような実装をしちゃうとヤバイですね。

Handler mHandler = new Handler();
String mMessage = "hello!";
private void hello(){
    mHandler.post(new Runnable() {
        @Override
        public void run() {
            Toast.makeText(getApplicationContext(), mMessage, Toast.LENGTH_SHORT).show();
        }
    });
    mMessage = "good bye!";
}
private void hello2(){
    runOnUiThread(new Runnable(){
        @Override
        public void run() {
            Toast.makeText(getApplicationContext(), mMessage, Toast.LENGTH_SHORT).show();
        }
    });
    mMessage = "good bye!";
}

別スレッドからhello(),hello2()、UIスレッドからhello(),hello2()を実行するとToastに何が表示されるでしょうか?

呼び出しパターン 結果
別スレッドからHandler#postを呼ぶ(hello()) "good bye!"が表示される
UIスレッドからHandler#postを呼ぶ(hello()) "good bye!"が表示される
別スレッドからrunOnUiThreadを呼ぶ(hello2()) "good bye!"が表示される
UIスレッドからrunOnUiThreadを呼ぶ(hello2()) "hello!"が表示される

なんか一個動きが違うのがいるーーーーー。
はいそういう事ですね。


結論


色々省略しますが
・Handler#post, runOnUiThreadに渡すRunnableが触る変数にはHandler#post, runOnUiThread呼び出し後触らない。
・Handler#post, runOnUiThreadはなるべくメソッドの最後に呼ぶ
・Handler#post, runOnUiThreadに渡すRunnableの中では呼び出し元のメンバ変数などを触らない。
Handler#post, runOnUiThreadを呼び出すメソッド内でfinalな変数を宣言しておいてそれにアクセスしたりする様にする。

とかやってるといいんじゃないでしょかー


追記



といったリプライが。確かに。(@atsushienoさんありがとうございました!)

実際hello(), hello2()のmMessage = "good bye!";の直前にThread.sleep(1000);を追加して実行してみるとどうなるか実験してみました。
結果は以下です

呼び出しパターン 結果
別スレッドからHandler#postを呼ぶ(hello()) "hello!"が表示される
UIスレッドからHandler#postを呼ぶ(hello()) "hello!"が表示される
別スレッドからrunOnUiThreadを呼ぶ(hello2()) "good bye!"が表示される
UIスレッドからrunOnUiThreadを呼ぶ(hello2()) "hello!"が表示される

なんか一個動きが違うのがいるーーーーー。
はいそういう事ですね。
以下の様な事になります。

呼び出しパターン 動き
別スレッドからHandler#postを呼ぶ(hello()) 呼び出し後の処理とRunnableの実行どちらが先かは判らない
UIスレッドからHandler#postを呼ぶ(hello()) 必ず呼び出し後の処理が実行された後にRunnableが実行される
別スレッドからrunOnUiThreadを呼ぶ(hello2()) 呼び出し後の処理とRunnableの実行どちらが先かは判らない
UIスレッドからrunOnUiThreadを呼ぶ(hello2()) 必ずRunnableが実行された後に呼び出し後の処理が実行される


結論は変わらず。
どちらにせよHandler#post, runOnUiThreadを呼び出した後にHandler#post, runOnUiThread内部で利用されうる値などの書き換えはやばいよ、という事ですね。
多分誰もこういう事はしないとは思いますが・・・。多分。


2011年11月9日水曜日

JavaによるErrorSortの実装

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク
また画期的なソートアルゴリズムが出ましたね。
[Python]SleepSort、BogoSortに続く画期的なソートアルゴリズム、ErrorSortを考えた
で、こういうネタソート、何か作れないかなーと思って考えていたら、「あっ、そうだ。エラーを使ってソートすればいいじゃないか」と思って実装してみた。

面白いので早速Javaで実装してみました。


ErrorSort


ソートはintでやる事になるのでTをintに変換するインタフェースを設けました。
try-cacthの部分で ToインタフェースのIntメソッドを呼び出してTのint表現をもらって配列を作り、
インクリメントされていくindexで配列にアクセスしてIndexOutOfBoundsExceptionを起こさせています。

 public interface To<T> {
  public int Int(T t);
 }
 public static <T> List<T> errorSort(List<T> list, To<T> to){
  List<T> copy = new ArrayList<T>(list);
  List<T> result = new ArrayList<T>();
  int size = copy.size();
  int count = 0;
  int index = 0;
  WHILE:while(count < size){
   for(T t : copy){
    try{
     (new byte[to.Int(t)])[index] = 0;
     }catch(IndexOutOfBoundsException e){
     result.add(t);
     copy.remove(t);
     count++;
     continue WHILE;
    }
   }
   index++;
  }
  return result;
 }


実行


以下の様に使います。

 public static void main(String[] args){
  List<Integer> list = Arrays.asList(new Integer[]{2,4,60,3,23,44});
  list = errorSort(list, new To<Integer>(){
   @Override
   public int Int(Integer t) {
    return t;
   }
  });
 }

結果
2,3,4,23,44,60,


結論


まだまだいろんなソートアルゴリズムがありそうですね。

2011年11月7日月曜日

CoffeeScriptでライフゲーム作った

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク
CoffeeScriptおもしれー
という事でライフゲームを作成しました。
HTML側はもう書かずエレメントの生成も全部CoffeeScriptでやってみました。

ライフゲーム


lifegame - jsdo.it - share JavaScript, HTML5 and CSS


コード


ライフゲームのルールはWikipediaで→ライフゲーム - Wikipedia

CoffeeScriptの実装はこの辺を適当に参照しつつ。

#util methods
create = (type) -> document.createElement(type)
append = (child) -> document.body.appendChild(child)
br = -> append(create("br"))
text= (type, text) ->
 elm = create(type)
 elm.innerHTML = text
 append(elm)
input = (type, value) ->
 elm = create("input")
 elm.type=type
 elm.value = value
 elm
button= (value, fn) ->
 elm = input("button", value)
 elm.onclick = fn
 append(elm)
 
#consts
CELL_SIZE = 5
BOARD_SIZE = 350
LENGTH = BOARD_SIZE/CELL_SIZE

isStart = false

cells = new Array(Math.pow(LENGTH, 2))
for i in [0.. cells.length-1]
 cells[i] = {"now":0, "next":0}

#methods
draw = (canvas, context) ->
 context.fillStyle="#eeeeee"
 context.fillRect(0,0, canvas.width, canvas.height)
 context.fill()
 
 context.fillStyle="#000000"
 context.strokeStyle="#000000"
 context.lineWidth = 1
 x = canvas.width/CELL_SIZE
 y = canvas.height/CELL_SIZE
 for i in [0.. x]
  context.beginPath()
  context.moveTo(i*CELL_SIZE , 0)
  context.lineTo(i*CELL_SIZE , canvas.height)
  context.stroke()
 for j in [0.. y]
  context.beginPath()
  context.moveTo(0, j*CELL_SIZE)
  context.lineTo(canvas.width, j*CELL_SIZE)
  context.stroke()   
 for c,i in cells
  if c.now == 1
   x = Math.floor(i % LENGTH)
   y = Math.floor(i / LENGTH)
   context.fillRect(x*CELL_SIZE, y*CELL_SIZE, CELL_SIZE, CELL_SIZE)

initialize = ->
 canvas = document.createElement("canvas")
 canvas.width = BOARD_SIZE
 canvas.height = BOARD_SIZE
 context = canvas.getContext("2d")
 canvas.onmousedown = (e) -> 
  x = Math.floor(e.offsetX / CELL_SIZE)
  y = Math.floor(e.offsetY / CELL_SIZE)
  if cells[x+(y*LENGTH)].now == 0
   cells[x+(y*LENGTH)].now = 1
  else
   cells[x+(y*LENGTH)].now = 0
  draw(canvas, context)
 append(canvas)
 draw(canvas, context)
 br()
 text("span","speed")
 speed = input("text", "10")
 speed.size = 10
 append(speed)
 br()
 button("start", ->
  if !isStart 
   isStart = true
   start(speed.value)
 )
 button("stop", ->
  isStart = false  
 )
 button("one step", ->
  if !isStart
   start(speed.value)
 )
 button("reset", ->
  for i in [0.. cells.length-1]
   cells[i] = {"now":0, "next":0}
  draw(canvas, context)
 )
 button("random", ->
  for i in [0.. cells.length-1]
   if Math.floor(Math.random() * 10) + 1 > 3
    cells[i].now = 0
   else
    cells[i].now = 1
  draw(canvas, context)
 )
 start = (speed) ->
  calc()
  draw(canvas, context)
  if isStart
   setTimeout(-> 
    start(speed)
   , 1000/speed)
 calc = ->
  for c, i in cells
   count = add(i-1)
   count+=add(i+1)
   count+=add(i-1-LENGTH)
   count+=add(i+1-LENGTH)
   count+=add(i-LENGTH)
   count+=add(i-1+LENGTH)
   count+=add(i+1+LENGTH)
   count+=add(i+LENGTH)
   switch count
    when 0,1,4,5,6,7,8
     c.next = 0
    when 3
     c.next = 1
    when 2
     c.next = c.now
  for c in cells
   c.now = c.next
 add = (index)->
  if index >= 0 && index < cells.length
   cells[index].now
  else
   0
window.onload= ->
 initialize()



結論


CoffeeScriptいい!

2011年11月5日土曜日

Chromium(ブラウザ)をソースからビルドする for Mac

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク
2011年11月1日(火)にパシフィコ横浜で開催されたGoogle Developer Dayに行って来ました。
で、デベロッパーツールのティップス・アンド・トリックスというセッションでChromeのデベロッパーツールのtipsなどを聞いて「あーChrome面白いなー」と思ったので早速コード落としてビルドしようと思って既に数日過ぎました。
最近Mac Book Airを買ったのでMacでビルドします。


Chromiumについて


Chromiumはhttp://dev.chromium.org/でオープンソースプロジェクトとして開発されています。
ブラウザであるChromiumとOSであるChromium OSは別々になっています。
Macでのビルド方法はhttp://code.google.com/p/chromium/wiki/MacBuildInstructionsに書かれていますが英語だしすぐ忘れるので備忘録として書いていきます。


環境構築


Chromiumをビルドする為に必要な環境を構築しましょう。

必要な環境


MacでChomiumのソースを取得し、ビルドする為に必要な環境は以下の通りです。

  • Intel Mac 10.6 (“Snow Leopard”)以上
  • XCode 3.2.3以上
  • gclient
  • git client(なくてもよいっぽい)


  • XCodeのインストール


    言わずと知れたIDEです。最初から入っている気もしますが入ってなかったりバージョンが足りてない場合はダウンロードしてインストールする必要があります。
    Apple Developer Connectionのアカウントが必要です。
    http://developer.apple.com/xcode/でゴニョゴニョして入手して下さい。
    LionならApp Storeから入手も可能。無料で手に入ります。


    gclientのインストール


    depot_toolsと呼ばれるパッケージに含まれるgclientというモノ。
    以下のsvnコマンドで取得します。
    svn co http://src.chromium.org/svn/trunk/tools/depot_tools
    

    depot_toolsをPATHに通しておきましょう。
    vi ~/.bash_profile
    
    export PATH=$PATH:〜〜/depot_tools
    


    gitのインストール


    多分最初から入ってる気がするけどこの辺りを参照してインストールしてください。


    コードの取得


    gclientを使ってChromiumのソースを取得してみます。
    gclientの他にtarballで配布されていたりgit cloneしたりも出来ます。

    まずソースを落としてくる為のディレクトリを作成します。名前はなんでもいいです
    mkdir chromium
    

    作ったディレクトリの中に移動し、以下のコマンドを実行します。
    cd chromium
    gclient config http://src.chromium.org/svn/trunk/src
    

    chromiumディレクトリ内に.gclientというファイルが生成されます。
    以下のコマンドを実行する事でソースをダウンロードする事ができます。
    gclient sync
    

    srcというディレクトリが生成され、その中にソースコードがダウンロードされます。


    ビルド


    ソースコードのダウンロードが完了したらいよいよビルドです。

    ビルドの準備


    LionやXCode4系の場合いろいろ問題があるっぽいのでここではClangを使ってビルドをします。
    srcディレクトリに移動し、以下のコマンドを実行し準備をします。
    cd src
    
    GYP_GENERATORS=make GYP_DEFINES=clang=1 ./build/gyp_chromium
    

    Lionを利用している場合は以下のコマンドを実行して下さい。
    GYP_GENERATORS=make GYP_DEFINES=mac_sdk=10.6 ./build/gyp_chromium
    


    デバッグビルド


    ビルドの準備ができたらmakeでビルドします。
    make chrome -j4
    

    chromium/src/out/DebugにChromium.appが作成されます。


    リリースビルド


    リリースビルドは以下のコマンド
    BUILDTYPE=Release make chrome -j4
    

    chromium/src/out/ReleaseにChromium.appが作成されます。


    実行


    わーい。
    デバッグビルドだとかなり動作が重いです。
    リリースビルドだと大体現在リリースされているChromeと同じ様な速度が出ます。