English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية

Androidのメッセージングメカニズムの詳細とサンプルコード

Android メッセージメカニズム

1.概要

Androidアプリの起動時には、デフォルトでメインスレッド(UIスレッド)が存在し、そのスレッドにはメッセージキュー(MessageQueue)が関連付けられます。すべての操作はメッセージキューに封装され、メインスレッドに処理を委譲されます。メインスレッドが終了しないように、メッセージキューの操作はループの中に置かれます。プログラムはループを常に実行し、ループの度に内部のメッセージキューからメッセージを取り出し、対応するメッセージ処理関数(handlerMessage)をコールバックします。メッセージが処理されると、ループに戻り、次のメッセージを待ち受けるようになります。メッセージキューが空の場合、スレッドはブロックして待ちます。したがって、終了しません。以下の図を参照してください:

Handler、Looper、Messageの関係は何ですか?

サブスレッドで時間がかかる操作を完了する場合、多くの場合UIを更新する必要があります。最も一般的なのは、Handlerを通じてメッセージをUIスレッドにポストし、その後HandlerのhandlerMessageメソッドで処理を行うことです。各Handlerはメッセージキュー(MessageQueue)に関連付けられ、LooperはMessageQueueを作成する責任を持ちます。デフォルトでは、MessageQueueは1つしかありませんが、それはメインスレッドのメッセージキューです。

これがAndroidのメッセージメカニズムの基本的な原理です。もっと詳細に知りたい場合は、ソースコードから見てみましょう。

2.ソースコードの解説

(}1)ActivityThreadのメインスレッドでメッセージループのLooperを起動します

public final class ActivityThread {
  public static void main(String[] args) {
    //コード省略
    //1.メッセージループのLooperを作成します
    Looper.prepareMainLooper();
    ActivityThread thread = new ActivityThread();
    thread.attach(false);
    if (sMainThreadHandler == null) {
      sMainThreadHandler = thread.getHandler();
    }
    AsyncTask.init();
    //2.メッセージループを実行します
    Looper.loop();
    throw new RuntimeException("Main thread loop unexpectedly exited");
  }
}

ActivityThreadはLooper.prepareMainLooper()を通じてメインスレッドのメッセージキューを作成し、最後にLooper.loop()を実行してメッセージキューを起動します。Handlerはメッセージキューとスレッドを関連付けます。

(}2)Handlerはメッセージキューとスレッドを関連付けます

public Handler(Callback callback, boolean async) {
    //コード省略
    //Looperを取得
    mLooper = Looper.myLooper();
    if (mLooper == null) {
      throw new RuntimeException(
        "Can't create handler inside thread that has not called Looper.prepare()"
    }
    //メッセージキューを取得
    mQueue = mLooper.mQueue;
  }

Handlerは内部でLooper.getLooper()メソッドを通じてLooperオブジェクトを取得し、それに関連付け、メッセージキューを取得します。それではLooper.getLooper()はどのように動作するのでしょうか?

  public static @Nullable Looper myLooper() {
    return sThreadLocal.get();
  }
  public static @NonNull MessageQueue myQueue() {
    return myLooper().mQueue;
  }
  public static void prepare() {
    prepare(true);
  }
  //現在のスレッドにLooperを設定します
  private static void prepare(boolean quitAllowed) {
    if (sThreadLocal.get() != null) {
      throw new RuntimeException("Only one Looper may be created per thread");
    }
    sThreadLocal.set(new Looper(quitAllowed));
  }
  //UIスレッドのLooperを設定します
  public static void prepareMainLooper() {
    prepare(false);
    synchronized (Looper.class) {
      if (sMainLooper != null) {
        throw new IllegalStateException("The main Looper has already been prepared.");
      }
      sMainLooper = myLooper();
    }
  }

Looperクラスでは、myLooper()メソッドはsThreadLocal.get()を通じて取得され、prepareMainLooper()でprepare()メソッドが呼び出されます。このメソッドではLooperオブジェクトが作成され、sThreadLocal()に設定されます。これにより、キューがスレッドに関連付けられます。sThreadLocal.get()メソッドを通じて、異なるスレッドがお互いのメッセージキューにアクセスできないことを保証します。

なぜUIのHandlerを主スレッドで作成する必要があるのか?

Handlerが主スレッドのメッセージキューに関連付けられるようにしなければならないのは、handlerMessageがUIスレッドで実行されるためであり、その場合、UIスレッドが安全になります。

(}3メッセージループ、メッセージ処理

メッセージループの構築はLooper.loop()メソッドを通じて行われます。ソースコードは以下の通りです:

/**
   * このスレッドでメッセージキューを実行します。必ず呼び出してください
   * {@link #quit()} でループを終了します。
   */
  public static void loop() {
    final Looper me = myLooper();
    if (me == null) {
      throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
    }
    //1.メッセージキューを取得
    final MessageQueue queue = me.mQueue;
    //2.死循环、つまりメッセージループ
    for (;;) {
      //3.メッセージを取得、ブロックされる可能性があります
      Message msg = queue.next(); // ブロックされる可能性があります
      if (msg == null) {
        // メッセージがないことは、メッセージキューが終了していることを示します。
        return;
      }
      //4.メッセージ処理
      msg.target.dispatchMessage(msg);
      //メッセージをリサイクルします
      msg.recycleUnchecked();
    }
  }

上記のプログラムから分かるように、loop()メソッドの本質はループを作成し、メッセージキューからメッセージを順次取り出し、最後に処理するものです。Looperに関しては、Looper.prepare()を通じてLooperオブジェクト(メッセージキューはLooperオブジェクトにエンキャップされています)を作成し、sThreadLocalに保存し、Looper.loop()を通じてメッセージループを実行します。これらの二つのステップは通常組み合わせて使用されます。

public final class Message implements Parcelable {
  //target処理
  Handler target; 
  //Runnable型のcallback
  Runnable callback;
  //次のメッセージ、メッセージキューはリンクリスト形式で保存されています
  Message next;
}

ソースコードから見ると、targetはHandler型です。実際には一巡して、Handlerを通じてメッセージをメッセージキューに送信し、メッセージキューがメッセージをHandlerに処理するように分割します。Handleクラスでは:

//メッセージ処理関数、サブクラスでオーバーライド
public void handleMessage(Message msg) {
}
private static void handleCallback(Message message) {
    message.callback.run();
  }
//メッセージ配信
public void dispatchMessage(Message msg) {
    if (msg.callback != null) {
      handleCallback(msg);
    }
      if (mCallback != null) {
        if (mCallback.handleMessage(msg)) {
          return;
        }
      }
      handleMessage(msg);
    }
  }

上記のプログラムから分かるように、dispatchMessageは単なる配信メソッドであり、Runnable型のcallbackが空であればhandleMessageを実行してメッセージを処理します。このメソッドが空であれば、UIを更新するコードをこの関数に書きます;callbackが空でない場合、handleCallbackを実行して処理します。このメソッドはcallbackのrunメソッドを呼び出します。これはHandlerが配信する二つのタイプの一つであり、例えばpost(Runnable callback)ではcallbackが空でないことがあります。Handlerを使ってsendMessageを行う際には通常callbackを設定しませんので、handleMessageが実行されます。

 public final boolean post(Runnable r)
  {
    return sendMessageDelayed(getPostMessage(r), 0);
  }
  public String getMessageName(Message message) {
    if (message.callback != null) {
      return message.callback.getClass().getName();
    }
    return "0x" + Integer.toHexString(message.what);
  }
  public final boolean sendMessageDelayed(Message msg, long delayMillis)
  {
    if (delayMillis < 0) {}}
      delayMillis = 0;
    }
    return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
    MessageQueue queue = mQueue;
    if (queue == null) {
      RuntimeException e = new RuntimeException("
          this + "sendMessageAtTime() called with no mQueue");
      Log.w("Looper", e.getMessage(), e);
      return false;
    }
    return enqueueMessage(queue, msg, uptimeMillis);
  }

上記のプログラムから見て取れるように、post(Runnable r)の際にはRunnableをMessageオブジェクトに包装し、RunnableオブジェクトをMessageオブジェクトのcallbackに設定し、最後にそのオブジェクトをメッセージキューに挿入します。sendMessageの実装も同様です:

public final boolean sendMessage(Message msg)
  {
    return sendMessageDelayed(msg, 0);
  }

RunnableをpostするかMessageをpostするかに関わらず、sendMessageDelayed(msg, time)メソッドが呼び出されます。Handlerは最終的にメッセージをMessageQueueに追加し、LooperはMessageQueueからメッセージを不断に読み取り、HandlerのdispatchMessageを呼び出してメッセージを配信します。このようにして、メッセージは次々と生成され、MessageQueueに追加され、Handlerによって処理され、Androidアプリが動作し始めます。

3.検証

new Thread(){
  Handler handler = null;
  public void run () {
    handler = new Handler();
  ;
.start();

上記のコードには問題がありますか?

LooperオブジェクトはThreadLocalのものです。つまり、各スレッドは自分自身のLooperを使用します。このLooperは空でなくても大丈夫ですが、サブスレッドでHandlerオブジェクトを生成する際にLooperが空であれば、例外が発生します。

public Handler(Callback callback, boolean async) {
    //コード省略
    //Looperを取得
    mLooper = Looper.myLooper();
    if (mLooper == null) {
      throw new RuntimeException(
        "Can't create handler inside thread that has not called Looper.prepare()"
    }
    //メッセージキューを取得
    mQueue = mLooper.mQueue;
  }

mLooperがnullの場合、例外をスロー。Looperオブジェクトが作成されていないため、sThreadLocal.get()はnullを返します。Handlerの基本原理は、MessageQueueと関連付け、メッセージをMessageQueueに投げ込むことです。MessageQueueがなければ、Handlerは存在する価値がありません。MessageQueueはLooperに封印されているため、Handlerを作成する際にはLooperがnullであることはできません。解決策は以下の通りです:

new Thread(){
  Handler handler = null;
  public void run () {
    //現在のスレッドにLooperを作成し、ThreadLocalにバインド
    Looper.prepare()
    handler = new Handler();
    //メッセージループを起動
    Looper.loop();
  ;
.start();

Looperを作成してメッセージループを起動しない場合、エラーメッセージは発生しませんが、handlerを通じてpostまたはsendMessage()を実行しても効果はありません。なぜなら、メッセージはメッセージキューに追加されますが、メッセージループは起動していないため、メッセージキューからメッセージを取得し実行することはありません。

ご読覧ありがとうございます。皆様のサポートに感謝します!

基礎教程
おすすめ