Home > android > AndroidのHandlerとは何か?

AndroidのHandlerとは何か?

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を通す事で例外を発生させなくできるかを説明します。

CalledFromWrongThreadException

まずは何故例外が発生するのかを見てみます。
基本的に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

さっそく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

突然出てきた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版 Effective Java 第2版
作者: Joshua Bloch (著), 柴田芳樹 (翻訳)
出版社/メーカー: ピアソンエデュケーション
発売日: 2008/11/27
メディア: 大型本
関連のありそうなエントリ

Comments:1

miduhima 09-06-30 (火) 23:02

とても詳しい解説ありがとうございます。ちょうど悩んでたところなので、有り難すぎて涙でそうです!!

Comment Form
Remember personal info

*
To prove that you're not a bot, enter this code
Anti-Spam Image

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 […]

Home > android > AndroidのHandlerとは何か?

Twitter
Search
Feeds
Meta

Return to page top