English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية
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()を実行しても効果はありません。なぜなら、メッセージはメッセージキューに追加されますが、メッセージループは起動していないため、メッセージキューからメッセージを取得し実行することはありません。
ご読覧ありがとうございます。皆様のサポートに感謝します!