2011年4月3日日曜日

Android のスレッドの使い方を調べる

わからないなりに、スレッドの使い方を調べてみました。

スタート地点

まずは Android Developers のサイトから。 それっぽいトピックがあるので、ここから始めてみます。

Processes and Threads | Android Developers

簡単な例も載っています。 ここ からそのまま引用です。

public void onClick(View v) {
    new Thread(new Runnable() {
        public void run() {
            Bitmap b = loadImageFromNetwork("http://example.com/image.png");
            mImageView.setImageBitmap(b);
        }
    }).start();
}

処理を新しく作ったスレッドで実行する例のようです。

このやり方だと、できる事は限られますね。

もう少し柔軟なやり方

さらに読んでいくとこんな記述がありました。

To handle more complex interactions with a worker thread, you might consider using a Handler in your worker thread, to process messages delivered from the UI thread.

複雑なやりとりが必要な場合は Handler というものを使えばよさそうです。

さらりと見てみた限り

  • スレッドを生成
  • 新しく作ったスレッド上で Handler を生成
  • Handler に対して sendMessage() を行う
  • handleMessage() が新しいスレッド上で実行される!

という感じになりそうです。

コードを書いてみる

さっそくコードを書いてみます。 まずは ThreadTest というクラスを作って、 コンストラクタ内でスレッドと Handler を生成します。

public class ThreadTest {
    private Thread mThread;
    private Handler mHandler;
    public ThreadTest() {
        Log.d("TEST", "+ ThreadTest() " + Thread.currentThread().toString());
        // スレッド生成
        mThread = new Thread(new Runnable() {
                public void run() {
                    Log.d("TEST", "+ run() " + Thread.currentThread().toString());
                    // Handler 生成
                    mHandler = new MyHandler();
                    Log.d("TEST", "- run() " + Thread.currentThread().toString());
                }
            });
        // スレッド開始
        mThread.start();
        Log.d("TEST", "- ThreadTest() " + Thread.currentThread().toString());
    }
}

次は Handler に対して Message を送るコードです。

Handler には Message を送るやり方と、 Runnable を送るやり方が用意されているようです。 今回は Message を送るやり方を試してみます。

public void doInMyThread() {
    Log.d("TEST", "+ DoInMyThread() " + Thread.currentThread().toString());
    // Bundle を生成して文字列をセット
    Bundle bundle = new Bundle();
    bundle.putString("mystring", "Hello!");
    // 空の Message (what=1) を取得 (new するより低コストらしい)
    Message msg = mHandler.obtainMessage(1);
    // Message に Bundle をセット
    msg.setData(bundle);
    // Message 送信
    mHandler.sendMessage(msg);
    Log.d("TEST", "- DoInMyThread() " + Thread.currentThread().toString());
}

次は Handler のコードです。

class MyHandler extends Handler {
    @Override
    public void handleMessage(Message msg) {
        super.handleMessage(msg);
        Log.d("TEST", "+ handleMessage() what=" + msg.what + " " + Thread.currentThread().toString());
        if (msg.what == 1) {
            Bundle bundle = msg.getData();
            Log.d("TEST", bundle.getString("mystring"));
        }
        Log.d("TEST", "- handleMessage() " + Thread.currentThread().toString());
    }
}

呼び出し側のコードです。

:
mThreadTest = new ThreadTest();
mThreadTest.doInMyThread();
:

メッセージループが必要

さっそく実行すると、 Handler 生成のところでいきなりコケます。

例外の内容を見てみると、新しく作ったスレッドに、メッセージ処理のためのメッセージループが用意されていない事が原因のようです。 メッセージループを準備するには Looper を使えばよさそうです。

mThread = new Thread(new Runnable() {
        public void run() {
            Log.d("TEST", "+ run() " + Thread.currentThread().toString());
            // メッセージループの準備
            Looper.prepare();
            // Handler 生成
            mHandler = new MyHandler();
            Log.d("TEST", "new mHandler" + mHandler.toString());
            // メッセージループ開始
            Looper.loop();
            Log.d("TEST", "- run() " + Thread.currentThread().toString());
        }
    });

まだうまくいかない

これでうまく行くはずなんですが、またコケます。

obtainMessage() のところで、 NullPointerException です。 新しく作ったスレッド上で Handler を生成する前(mHandler == null の状態)にここに到達しているようです。

呼び出し側のスレッドをセマフォを使って待たせる必要がありそうです。

Semaphore が使えそうです。

public ThreadTest() {
    Log.d("TEST", "+ ThreadTest() " + Thread.currentThread().toString());
    // セマフォ生成
    mSemaphore = new Semaphore(0);
    // スレッド生成
    mThread = new Thread(new Runnable() {
            public void run() {
                Log.d("TEST", "+ run() " + Thread.currentThread().toString());
                // メッセージループの準備
                Looper.prepare();
                // Handler 生成
                mHandler = new MyHandler();
                // 待ちを解除
                mSemaphore.release();
                // メッセージループ開始
                Looper.loop();
                Log.d("TEST", "- run() " + Thread.currentThread().toString());
            }
        });
    // スレッド開始
    mThread.start();
    // 新しいスレッドの準備が整うまで待つ
    mSemaphore.acquire();
    Log.d("TEST", "- ThreadTest() " + Thread.currentThread().toString());
}

ようやく動いた

ようやく動きました。

D/TEST    (19983): + ThreadTest() Thread[main,5,main]
D/TEST    (19983): + run() Thread[Thread-10,5,main]
D/TEST    (19983): - ThreadTest() Thread[main,5,main]
D/TEST    (19983): + DoInMyThread() Thread[main,5,main]
D/TEST    (19983): - DoInMyThread() Thread[main,5,main]
D/TEST    (19983): + handleMessage() what=1 Thread[Thread-10,5,main]
D/TEST    (19983): Hello!
D/TEST    (19983): - handleMessage() Thread[Thread-10,5,main]

ログを見ると、 handleMessage() が新しく作ったスレッド(Thread[Thread-10,5,main])で動いている事がわかります。

わかった事

  • Handler を介して、スレッド間でメッセージのやりとりができる
  • Handler を使うにはスレッドにメッセージループが必要 (Looper を使う)
  • 同期が必要な場合は Semaphore が使える

もう少し簡単なやり方はありそうですね。あと、メッセージループから抜けていないようなんですが。 これはリソースリークになってそう…。その内調べたいと思います。

0 件のコメント:

コメントを投稿