概要
ドキュメントの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); } });
最終更新:2007年11月18日 12:22