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のメソッドが実行されることになります。