メモ > Looper

「メモ/Looper」の編集履歴(バックアップ)一覧はこちら

メモ/Looper」(2007/11/18 (日) 12:22:32) の最新版変更点

追加された行は緑色になります。

削除された行は赤色になります。

* 概要 ドキュメントの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 にあるような、 呼び出し側が待機して同期する機能が不足している。
* 概要 ドキュメントの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 もご覧ください。