別スレッドで動かす 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 スレッドが割り込まれたときの処理は、割り込みを無視して再度待機するとしているけれど、これは利用場面にあわせて書き換えてほしい。