2011年04月07日
AndroidのLooperとHandlerの実装
今回はAndroidのLooperとHandlerの話。
LooperはActivityのメインスレッドでも使われています。それはstacktraceを見るとわかります。(こちら)
スレッド間のメッセージの受け渡しに使われています。こちらのページがわかりやすいです。
今回はこの実装を見ていきます。
JavaDocに載っているLooperとHandlerの使い方の例です。
class LooperThread extends Thread { public Handler mHandler; public void run() { Looper.prepare(); mHandler = new Handler() { public void handleMessage(Message msg) { // process incoming messages here } }; Looper.loop(); }
私はこれを最初に見たときに、とても違和感を感じました。
このコードのここだけを見ると、HandlerとLooperが無関係のもののように見えます。
HandlerとLooperはどのようにして対応づけられているのでしょうか?
中のコードを追いかけてみます。まずは Looper.prepare()です。
public static final void prepare() { if (sThreadLocal.get() != null) { throw new RuntimeException("Only one Looper may be created per thread"); } sThreadLocal.set(new Looper()); }
このstatic methodの中でLooperのインスタンスを作成し、それをsThreadLocalのセットしていました。ひとつのスレッドには複数のLooperをセットできないようになっています。
sThreadLocalは何かと言えば、クラスのロード時に初期化されるクラス変数で、その型はjava.lang.ThreadLocalです。
public class Looper { ... // sThreadLocal.get() will return null unless you've called prepare(). private static final ThreadLocal sThreadLocal = new ThreadLocal(); ...
そして、これのgetterメソッドは以下の通り。
public static final Looper myLooper() { return (Looper)sThreadLocal.get(); }
一方、Handlerの方は
Handler.java
public Handler() { ... mLooper = Looper.myLooper(); if (mLooper == null) { throw new RuntimeException( "Can't create handler inside thread that has not called Looper.prepare()"); } mQueue = mLooper.mQueue; mCallback = null; }
コンストラクタの中で、Looper.myLooper()を呼んでいます。これによってHandlerを生成したときのスレッドのLooperと対応がつきました。
最後にLooper.loopです。
Looper.java
public static final void loop() { Looper me = myLooper(); MessageQueue queue = me.mQueue; while (true) { Message msg = queue.next(); // might block if (msg != null) { if (msg.target == null) { // No target is a magic identifier for the quit message. return; } ... msg.target.dispatchMessage(msg); ... msg.recycle(); } } }
ループの中で、queueからメッセージを取り出して、それを実行しています。
quereにメッセージを入れるのはHandlerのインスタンスを使います。
つまり、LooperとHandlerを同じスレッドで生成しておけば、それで対応がつくので、後はHandlerのインスタンスをどこでも好きなスレッドに渡せば、どのスレッドからでも対応するLooperのスレッドにメッセージを送ることができるわけです。
Androidのframeworkのソースコードを見ると、いろいろなところでLooperとHandlerが使われています。
ActivityのonStartなどの状態遷移のときには、ActivityMananagerからのプロセス間メソッド呼び出しをActivity内のBinderThreadが実行しますが、ここでメインスレッドのHandlerのインスタンスを使ってLooperにメッセージを送信することで、メインスレッドでonStartのメソッドが実行されることになります。