メモ > Looper


※上記の広告は60日以上更新のないWIKIに表示されています。更新することで広告が下部へ移動します。

概要

ドキュメントのHowtoに次のような一節がある。
Setting Alarms
Android provides an AlarmManager service that will let you specify an Intent to send at a designated time. This intent is typically used to start an application at a preset time. (Note: If you want to send a notification to a sleeping or running application, use Handler instead.)
しかしサンプルではごく一部にHandlerが登場するだけで、その使い方がよく分からなかった。
Looper を中心にスレッドメッセージキューの実装を調査してみる。

Looper

LooperクラスにはstaticなThreadLocal変数が定義されている。
クラスメソッド Looper.prepare() はこの変数にLooperオブジェクトを格納する。prepareできるのはスレッドごとに最初の1回だけで、2回目以降はRuntimeExceptionになる。
Looperのコンストラクタはプライベートメソッドであるため、Looper.prepare()以外の方法でLooperオブジェクトを作成することはできない。

static Looper.myLooper() はprepareされたLooperオブジェクトの参照を返す。
static Looper.myQueue() はprepareされたLooperオブジェクトの中にあるMessageQueueの参照を返す。
quit() は対象のLooperの中のキューに特別なメッセージを MessageQueue.enqueueMessage する。
loop()はブロックし、特別なメッセージやエラーが発生するまでメッセージキューの中身を Message.target.dispatchMessage(msg) し続ける。使い終わったメッセージは Message.recycle() される。

Handler

Looperのメッセージキューにメッセージを追加する際は、同一パッケージ中のコードでなければ HandlerのsendMessageAtTime()かsendMessageAtFrontOfQueue()を経由することになる。この2つのメソッドはどちらもMessage.target をHandlerオブジェクト自身に上書きする。なので、Looper.loop()がメッセージをディスパッチする際にはメッセージを送るのに使ったHandlerが必ず関わる。
Handler.dispatchMessage() は、メッセージにコールバックが設定されていればそれを呼び、でなければHandler.handleMessage()を呼びだす。Handlerの派生クラスを作成することでコールバックより効率の良いメッセージハンドリングが可能になるわけだ。

MessageQueue

MessageQueueはMessage内部のリンク構造を管理することでヒープ管理コストを軽減している。MessageQueue.enqueueMessage のアクセス権はpackage であるため、ユーザが直接利用することはできない。
メッセージは処理時刻順にキューのなかでソートされている。
addIdleHandler() でidleHandlerを複数登録できる。

用途

Activity の onCreate() で Looper.prepare() してみると 例外がでて
Only one Looper may be created per thread
というエラーメッセージが得られる。
アプリケーションのメインスレッドがLooperを使っていることは間違いない。
メインスレッド上でLooper.myLooper()を使用してLooperオブジェクトを取得して、
それを扱うHandlerを構築すれば、他のスレッドから任意のcallbackをメインスレッド上で動作させることができる。
これはIntentを使うよりもスマートなやり方だ。

おまけ


WindowsのSendMessageや J2SEのSwingUtilities.invokeAndWait にあるような
呼び出し側が待機して同期する機能は次のようなコードで実現できる。

Handlerにpostした後、待機するコード:
class InvokeKnock implements Runnable{
 private InvokeKnock(){}
 private Runnable mCallback;
 private CountDownLatch mCount;
 @Override public void run(){
  try{ mCallback.run(); }catch(Throwable e){ Log.e("InvokeKnock","error in callback.",e); }
  mCount.countDown();
 }
 // スレッドのHandlerにcallbackをpostした後、その処理が終わるまで待つ
 public static void call(Handler h,Runnable callback){
  // 呼び出し元と呼び出し先が同じスレッドならキューを通さずに直接実行する
  if( Looper.myLooper() == h.getLooper() ){
   try{ callback.run(); }catch(Throwable e){ Log.e("InvokeKnock","error in callback.",e); }
   return;
  }
  // countDown()と組み合わせたRunnableをHandlerにポスト
  InvokeKnock iw = new InvokeKnock();
  iw.mCallback = callback;
  iw.mCount    = new CountDownLatch(1);
  h.post(iw);
  // 処理されるのを待つ
  try{ iw.mCount.await(); }catch(InterruptedException e){
     Log.d("InvokeKnock","calling thread is interrupted while waiting.");
     Thread.currentThread().interrupt(); // 再送出
  }
 }
};
使用例
(メインスレッド)
handler = new Handler(Looper.myLooper());
(別スレッド)
 ... catch(final Throwable e){ InvokeKnock.call( handler,new Runnable(){ @Override public void run(){ onError(e); } });
ツールボックス

下から選んでください:

新しいページを作成する
ヘルプ / FAQ もご覧ください。