- 2009-06-11 1:14
- android
AndroidでWeb APIを使う場合、マルチスレッドによるユーザビリティ向上を以前のエントリで説明しました。
AndroidアプリのUIはシングル・スレッド モデルです。
単純にマルチスレッドにしてUIの操作をしてしまうと、CalledFromWrongThreadExceptionでアプリがダウンしてしまいます。
これを回避する仕組みがHandlerです。
Handlerの仕組みを簡単に説明しようと思ったのですが、
またもや長くなってしまったので、先にまとめます。
- AndroidのUI操作はシングル・スレッド モデル
- ユーザビリティ向上の為にはマルチスレッドが必要
- Handlerで実現
- Handlerを使わない場合に起きる例外は実行スレッドのチェックで発生
- Handlerを使うと、UI Threadの持つキューにジョブを登録できる
- キューはUI Threadにより実行される
- 別スレッドからUI Threadに処理を登録するのでスレッドチェックで例外が発生しない
Android SDK 1.5 (Cupcake)
まずはダメな例です。
public void onClick(View v) { new Thread(new Runnable() { //新規スレッドを作成 public void run() { Bitmap b = loadImageFromNetwork(); //ネットワーク上から画像をダウンロード mImageView.setImageBitmap(b); //画像をImageViewに設定(CalledFromWrongThreadException) } }).start(); }
一見正しく動きそうですが、別スレッドからUIを操作しているので上記の例外が発生します。
これを修正したのがコレです。
Handler mHandler = new Handler(); public void onClick(View v) { new Thread(new Runnable() { public void run() { final Bitmap b = loadImageFromNetwork(); mHandler.post(new Runnable() { public void run() { mImageView.setImageBitmap(b); } }); } }).start(); }
少し長いですが、これで正しく動作します。
本エントリーでは、何故Handlerを通す事で例外を発生させなくできるかを説明します。
まずは何故例外が発生するのかを見てみます。
基本的にinvalidate()で画面の描画を指示します。
例えば、TextView.setText(…)も、内部ではinvalidate()を呼出しています。
よって、invalidate()を見てみます。
// android.view.View.java public void invalidate() { ... final ViewParent p = mParent; ... p.invalidateChild(this, r); }
ということで、ViewParent.invalidateChild()が呼出されています。
ViewParentはinterfaceで、実体はViewRootです。
// android.view.ViewRoot.java public void invalidateChild(View child, Rect dirty) { checkThread(); ... }
checkThread()という怪しげなメソッドが…
// android.view.ViewRoot.java void checkThread() { if (mThread != Thread.currentThread()) { throw new CalledFromWrongThreadException( "Only the original thread that created a view hierarchy can touch its views."); } }
これが原因ですね。
実行スレッドとmThreadを比較して、異なれば別スレッドによるUI操作と判断するようです。
ちなみに、mThreadはViewRootのコンストラクタを呼出したスレッドのことです。
すなわちViewを構築するUI Thread(Main Thread)の事です。
// android.view.ViewRoot.java public ViewRoot(Context context) { ... mThread = Thread.currentThread(); ... }
これで、とてもシンプルな方法でUI操作のシングル・スレッド モデルをチェックしていることが分かりました。
ではHandlerを通す事で、このチェック文を通過できるのは何故でしょうか?
さっそく調べてみます。
さっそくHandler.post()を見てみます。
// android.os.Handler.java public final boolean post(Runnable r) { return sendMessageDelayed(getPostMessage(r), 0); }
どんどん掘って行きます。まずはgetPostMessage()。
private final Message getPostMessage(Runnable r) { Message m = Message.obtain(); m.callback = r; //Runnbleはcallbackに格納 return m; }
これはRunnableを持つMessageを作っているだけですね。
次に、sendMessageDelayed()。
public final boolean sendMessageDelayed(Message msg, long delayMillis) { if (delayMillis < 0) { delayMillis = 0; } return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis); }
現時間からdelayMillis(今回は0ms)だけ後に実行するようです。
次はsendMessageAtTime()。
public boolean sendMessageAtTime(Message msg, long uptimeMillis) { ... MessageQueue queue = mQueue; //UIスレッドが持つキュー ... msg.target = this; // targetフィールドに自分を仕込む sent = queue.enqueueMessage(msg, uptimeMillis); ... }
なにやらMessageQueueにMessageを入れているようです。
このMessageQueueはMessageをノードにしたリストを管理しているだけです。
LinkedListの様な形式です。
ただし、単純なキューではありません。
キューにMessageを追加する際、Messageリストの順番が必ず時間順になるようにしています。
この時間というのは、Messageが実行される時間で、上記メソッド引数のuptimeMillisです。
MessageのインスタンスフィールドのwhenにuptimeMillisが格納され、
その値と各ノードのMessageのそれとを比較して、順番を整列させます。
このMessageQueueはUIスレッドが持つMessageQueueです。
Activityには1つだけMessageQueueを持ち、それにMessageを追加します。
ここがキモですね!!
MessgeQueueはUIスレッドにつき1つだけ持っていて、
あらゆるスレッドからHandlerを通してpostしたRunnableオブジェクトがMessageオブジェクトに形を変えて、
MessageQueueに追加される仕組みです。図における右側がそれを意味しています。

この様に、Handler.post()は引数のRunnableをMessageに変換し、
UI Threadが持つキューに、時間順に登録するだけの処理という事が分かりました。
つまり、Handler.post()はRunnableを実行しているわけでは無いようです。
では、構築したMessageQueueはどこで処理されるのでしょうか?
突然出てきたLooper。
これ、実はAndroidアプリの根幹をなす部分です。
LooperはUI ThreadでActivityを起動する際に
Looperの主要メソッドのloop()を実行しています。
// android.os.Looper.java public static final void loop() { Looper me = myLooper(); MessageQueue queue = me.mQueue; while (true) { Message msg = queue.next(); // might block ... msg.target.dispatchMessage(msg); ... } }
ご覧の様に、whileで永遠に回り続けるメソッドです。名前のLooperに恥じないメソッドですね。
実際は無駄にグルグル回り続けることはなく、queue.next()メソッド内で適切にwait()が入るようになっています。
このqueue.next()によりMessageQueueから実行すべきMessageノードを取り出してます。
実行すべきというのは、Messageに設定されている実行時間と現時間を比較して、
現時間に達しているMessageのみをMessageQueueから取り出すことを意味しています。
もう一度図を見ると、左側がそれを意味しています。

Messgeの実行はmsg.target.dispatchMessage(msg)です。
これはLooperが実行しています。すなわち、UI Threadで実行するという事です。
msg.targetはHandlerでしたので、戻ってソースを見てみます。
// android.os.Handler.java public void dispatchMessage(Message msg) { if (msg.callback != null) { handleCallback(msg); } else { ... } }
msg.callbackにはpostで渡したRunnable入れたので、handleCallback()が実行されます。
// android.os.Handler.java private final void handleCallback(Message message) { message.callback.run(); }
これで解決です。
postに渡されてたRunnableがLooperにより実行されていることが分かりました。
LooperすなわちUI Threadによりrun()が実行されるので、
UI操作をしてもViewRoot.checkThread()を通過できるという仕組みです。
Handler.post()は引数のRunnableをMessageに変換して、MessageQueueに登録する。
UI Threadで実行されているLooper.loop()がMessageQueueに登録されたMessageを取り出し、実行する。
なんともシンプルですね。
理解しやすいように、MessageQueueはUI Threadが持っていると説明しました。
実装はもう少し複雑で、UI ThreadのThreadLocal領域にLooperを持っており、
そのLooperがMessageQueueを持っているというのが正しい構造です。
UI ThreadはZygoteが実行するmainメソッドを持つクラスで、ActivityThreadクラスがそれです。
このクラスのmainメソッドからLooper.loop()が実行されています。
// android.app.ActivityThread.java public final class ActivityThread { ... public static final void main(String[] args) { ... ActivityThread thread = new ActivityThread(); thread.attach(false); //アプリの起動 Looper.loop();//Looperの起動 ... } }
並行処理やJavaの高度なテクニックを学びたい人は「Effective Java」がオススメです。
何か良い本はありませんか?と聞かれるたびに、この本をお薦めしています。
|
Effective Java 第2版 作者: Joshua Bloch (著), 柴田芳樹 (翻訳) 出版社/メーカー: ピアソンエデュケーション 発売日: 2008/11/27 メディア: 大型本 |
- Newer: GDD PhoneでSimejiを使う方法
- Older: Google Developer Day 2009に行ってきました
Comments:1
- miduhima 09-06-30 (火) 23:02
-
とても詳しい解説ありがとうございます。ちょうど悩んでたところなので、有り難すぎて涙でそうです!!
Trackbacks:1
- Trackback URL for this entry
- http://www.adamrocker.com/blog/261/what-is-the-handler-in-android.html/trackback/
- Listed below are links to weblogs that reference
- AndroidのHandlerとは何か? from throw Life
- pingback from Timerを使って定期実行する - Tech Booster 10-08-17 (火) 2:11
-
[…] Handlerについては、adamrockerさんのWebサイト、throw Lifeにわかりやすい解説があります。 AndroidのHandlerとは何か? http://www.adamrocker.com/blog/261/what-is-the-handler-in-android.html […]

