別スレッドで動かす Handler
たいていの場合、 Handler を定義するときは UI スレッドを Looper として使う。この場合 Looper を明示的に渡す必要はなく、だから Handler が外側のクラスになる。
class Foo { private static final class MyHandler extends Handler { @Override public void handleMessage(Message msg) { ... } } private final Handler handler = new MyHandler(); private void hoge() { ... handler.sendEmptyMessage(...); ... } }
ところで Android の Looper の説明 http://developer.android.com/reference/android/os/Looper.html の例では LooperThread を定義して、その run() の中で無名 Handler を生成している。
ふだん Handler を最外部のクラスとして使っているので、一枚 LooperThread クラスがかぶさっているのがどうにも気持ち悪い。
というわけで Handler を外側のクラスにして、その内側に LooperThread を封じ込める実装をしてみた。
スレッド定義をクラス内部に封じた Handler クラス
class MyHandler extends Handler { private static final int MSG_QUIT = -1; // define other messages here // ... MyHandler() { super(new LooperThread().looper); } public void quit() { sendEmptyMessage(MSG_QUIT); final Thread thread = getLooper().getThread(); while (thread.isAlive()) { try { thread.join(); } catch (InterruptedException e) { } } } @Override public void handleMessage(Message msg) { switch (msg.what) { // handle other messages here // ... case MSG_QUIT: Looper.myLooper().quit(); break; } } private static final class LooperThread extends Thread { private static final int kPriority = android.os.Process.THREAD_PRIORITY_BACKGROUND; Looper looper; LooperThread() { start(); waitForSetLooper(); } @Override public void run() { Looper.prepare(); setLooperAndNotify(); android.os.Process.setThreadPriority(kPriority); Looper.loop(); } private synchronized void waitForSetLooper() { while (looper == null) { try { wait(); } catch (InterruptedException e) { } } } private synchronized void setLooperAndNotify() { looper = Looper.myLooper(); notifyAll(); } } }
LooperThread の開始
Handler に Looper を渡す機会は、コンストラクターの引数にするしかない。ということで Looper はあらかじめ生成しておく必要がある。ここでは LooperThread オブジェクトを生成して、そのメンバー looper を参照して渡している。
LooperThread オブジェクトを生成し終えた時点で looper メンバーが初期化されていることを保証するために、 LooperThread のコンストラクターでは looper を準備するスレッド(LooperThread#run() がその本体)を start() したあと、 waitForSetLooper() を呼んで looper が設定されるまで待ち合わせる。
起こされたスレッド LooperThread#run() は Looper.prepare() を呼んで Looper としての準備をし、 setLooperAndNotify() を呼びだして looper の設定を待ち合わせているスレッドに通知する。(この待機と通知は Java 標準の wait() と notifyAll() のペアで実現している)
looper の設定と通知を終えると LooperThread のスレッドは Looper.loop() を呼んで MessageHandler としての仕事を開始する。
LooperThread の終了
さて、この LooperThread の終了だけれど、これは MyHandler の quit() を呼べばよい。スレッドが複数からむオブジェクトの例に漏れず、これもわかりにくいのだけれど quit() は UI スレッドから呼ばれ、 Handler の handleMessage() は LooperThread から呼ばれる。
quit() を呼ぶとまず MSG_QUIT がメッセージキューに積まれる。その後 UI スレッドは LooperThread が停止するまで join() で待ち合わせる。
LooperThread のスレッド本体 LooperThread#run() は Looper.loop() の中から呼び返す MyHandler#handleMessage() を呼び出し続けており、先に積まれた MSG_QUIT に対応する case MSG_QUIT で Looper#quit() を呼び出す。これで Looper.loop() が終了するため LooperThread#run() は処理を終えスレッドは停止する。そして join() で待機していた UI スレッドが動き始め LooperThread のスレッド資源を回収する。
なお、 UI スレッドが割り込まれたときの処理は、割り込みを無視して再度待機するとしているけれど、これは利用場面にあわせて書き換えてほしい。