Android上級知識ツリー-Androidメッセージキュー


1、概要
Androidプログラムが起動すると、デフォルトでメインスレッドでプログラムが実行されます.それでは、時間のかかる操作を実行するとUIがブロックされ、インタフェースカートンの現象が発生します.また、ユーザーの多くの操作、システムがどのように処理されているのか、システムがどのようにこれらのタスクを管理しているのか、答えは今日のテーマAndroidのメッセージメカニズムです.
  • Androidメッセージの処理方法-handler、Looper、MessageQueue
  • Handler:タスクをキューに追加し、実行が終了するとメインスレッドでUI操作
  • を実行する.
  • Looper:バインドされていないスレッドはループメッセージキューを開き、メッセージ
  • を取得する.
  • MessageQueue:タスクキュー、送信メッセージ
  • を保存
  • ソースコード分析を行う前に、2つの一般的な疑問
  • を提出する.
  • サブスレッドが直接Handlerを作成すると例外が放出されるのはなぜですか?
  • Handlerはどのスレッドで実行されますか?

  • 2、ソース分析
    Handlerの使い方についてはここでは説明しませんが、メモリリークを避けるために静的Handlerを作成する必要があることに注意して使用します.ここではHandlerのソースコードを実行過程と原理の観点から簡単に分析します.まず、メインスレッドではHandlerを直接使用できることを知っていますが、サブスレッドではできません.なぜか考えたことがありますか.まずメインスレッドの違いを見てみましょうmain()
    ​
    public static void main(String[] args) {
            Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");
          ...
            Looper.prepareMainLooper();
            ActivityThread thread = new ActivityThread();
            thread.attach(false);
            if (sMainThreadHandler == null) {
                sMainThreadHandler = thread.getHandler();
            }
            if (false) {
                Looper.myLooper().setMessageLogging(new
                        LogPrinter(Log.DEBUG, "ActivityThread"));
            }
            Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
            Looper.loop();
            throw new RuntimeException("Main thread loop unexpectedly exited");
        }
    
    ​
  • 上記コードは、プログラムの起動後、3つのステップを実行することを示す:
  • .
  • Looperを呼び出す.prepareMainLooper();メッセージループの作成Looper
  • threadを使用する.getHandler(); UIスレッドを取得するHandler
  • Looperを使用loop()オープンメッセージループ
  • Handlerはメッセージキューに関連付けられており、メッセージキューはLooperにカプセル化されており、各Looperは1つのスレッドに関連付けられているので、Handlerのメッセージは概ねhandlerがメッセージプロセッサとしてメッセージキューに渡され、スレッド内のLooperは1つずつメッセージを取り出して実行し、ここでのスレッドはUIスレッドである.
  • Handlerの最も基本的な使い方は、Handlerを作成してMessageをキューに送信することです.Handlerを作成するときに何をしたかを見てみましょう.
  • ​
     public Handler(Callback callback, boolean async) {
            
            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 = callback;
            mAsynchronous = async;
        }
    
    ​
  • Looperを呼び出す.myLooper():Looperオブジェクトを取得し、空の場合は例外を放出してlopperを呼び出す.mQueneオープンメッセージキュー
  • myLooper()メソッドを表示する
  • public static @Nullable Looper myLooper() {
            return sThreadLocal.get();
        }
  • myLooperでは、LooperがsThreadLocalから取得していることがわかりますが、いつ設定されていますか?また、sThreadLocalにLooperのインスタンスがない限り異常が投げ出され、sThreadLocalは実行スレッドであり、言い換えればHandlerが存在するスレッドにLooperのインスタンスがない限り異常である.
  • UIスレッドの開始時に作成されたLooperオブジェクトのメソッドprepareMainLooper()を参照してください.
    ​public static void prepareMainLooper() {
            prepare(false);
            synchronized (Looper.class) {
                if (sMainLooper != null) {
                    throw new IllegalStateException("The main Looper has already been prepared.");
                }
                sMainLooper = myLooper();
            }
        }
  • prepareMainLooper()prepare()メソッドが呼び出されました:
  • ​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));
        }
  • で見たset(new Looper(quitAllowed))では、Looperオブジェクトを作成してsThreadLocalに設定しますが、上記のsThreadLocalがわかっているかどうか.get()で取得したLooperはどこですか.

  • ここではHandlerとLooperの作成とスレッドの関連付けについて説明し,メッセージループ取得タスクを開始して実行する方法を確認し,エントリメソッドの最後にLoopを呼び出す.loop()メソッド、次はLooperを表示します.loop()メソッド:
    public static void loop() {
            final Looper me = myLooper();
            final MessageQueue queue = me.mQueue;
    for (;;) {
                Message msg = queue.next(); // might block
                if (msg == null) {
                    return;
                }
    ...
     msg.target.dispatchMessage(msg);
    msg.recycleUnchecked();
    }
  • myLooper()を呼び出してLooperのオブジェクトを取得し、メッセージキューを取得するとnext()を呼び出してその中のMessageを順次取り出し、ここではデッドループ
  • であることに注意する.
  • Messageのmsgを呼び出す.target.dispatchMessage(msg)実行方法
  • Messageのソースコードを見るとtargetが実際にHandlerのオブジェクトであることがわかり、最後にHandlerを一周したdispatchMessage(msg)処理ロジック
  • を説明する.
    Handler target;
    Runnable callback;
    Message next;
  • HandlerのdispatchMessage(msg)法
  • ​public void dispatchMessage(Message msg) {
            if (msg.callback != null) {
                handleCallback(msg);
            } else {
                if (mCallback != null) {
                    if (mCallback.handleMessage(msg)) {
                        return;
                    }
                }
                handleMessage(msg);
            }
        }
    private static void handleCallback(Message message) {
            message.callback.run();
        }

    まとめてみると、looperはMessageQueneから順にMessageを取り出し、MsgバインドされたHandlerのdispatchMessage(msg)処理を呼び出し、dispatchMessage()で順次実行を判断する.
  • MessageのRunableオブジェクトcallbackがnullであるか否かを先に判断し、handleCallback()を空にしないでRunnableを呼び出すとrun()は
  • を実行する.
  • callbackが空判定mCallBackである、mCallBackが空でない場合mCallBackが呼び出される.handleMessage()処理
  • 両方がnullの場合はhandleMessage()が実行され、ここではHandler()書き換えを作成するhandlerMessage()
  • が実行される.
    このAndroidではHandlerのメッセージ処理について、Handler、Looper、MessageQueueの作成からメッセージの伝達やイベントの処理まで説明しましたが、使用時にMessageQueueにメッセージをどのように送るか、上記のcallback、mCallBackが空の場合を見てみましょう.
  • 作成Handler後のメッセージ送信方法:sendMessage(Message msg)、post(Runnable r)
  • sendMessage()
  • public final boolean sendMessage(Message msg)
        {
            return sendMessageDelayed(msg, 0);
        }
    
    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;
            
            return enqueueMessage(queue, msg, uptimeMillis);
        }
    
    private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
            msg.target = this;   //     target Handler  
            if (mAsynchronous) {
                msg.setAsynchronous(true);
            }
            return queue.enqueueMessage(msg, uptimeMillis);
        }

    上の実行プロセスコードが上から倒れるのが一目瞭然で、enqueueMessage()にMessageが設定されている.targetは呼び出しメソッドのHandlerであるため、dispatchMessage()を呼び出すとHandlerのhandleMessage()処理が呼び出される
  • post(Runnable r)
  • public final boolean post(Runnable r)
        {
           return  sendMessageDelayed(getPostMessage(r), 0);
        }
    
    private static Message getPostMessage(Runnable r) {
            Message m = Message.obtain();
            m.callback = r;   //   Message callback r
            return m;
        }
  • はgetPostMessage()を呼び出してMessageのcallbackを設定するので、callbackは送信されたrunnableであり、残りの方法はsendMessage()と一致する.

  • Handlerメッセージメカニズムの原理とプロセスの紹介が終わり、上の理解を持って、今上の2つの疑問を解決します.
  • サブスレッドでhandlerを直接作成できないのはなぜですか?

  • 上記のソースコード解析により,投げ出された異常が上記のコードにも現れていることが分かった.
    mLooper = Looper.myLooper();
            if (mLooper == null) {
                throw new RuntimeException(
                    "Can't create handler inside thread that has not called Looper.prepare()");
            }

    Looper.myLooper()が空になったときに異常を放出し、myLooper()が入ったときにsThreadLocal.get()で取得する、つまりこの時点でsThreadLocalでLooperオブジェクトが設定されていないため、後のキューなどの操作もなく、上からLooperオブジェクトの作成と設定はprepare()メソッドにあることがわかるので、サブスレッドでHandlerを使用するにはまずLooperを呼び出す.prepare()Looperオブジェクトを作成し、Looperを呼び出す.loop()メッセージループを開きます.
  • Handlerはどのスレッドで実行されますか?

  • 実はこの問題に対して、ネット上の多くの答えは、Handler()が存在するスレッドを実行することです.つまり、どのスレッドでHandlerインスタンスを作成するか、handlerの後続のタスクはどのスレッドで実行されますか.実は、この答えは間違いありません.私たちが普段使っていることから、サブスレッドがLoopを初期化した後にHandler()を作成するか、メインスレッドが直接作成するか、最終的にHandlerが実行するスレッドはスレッドを作成することですが、これは1つのケースにすぎません.次にHandlerの別の作成方法を見てみましょう.タスクの処理はlooperであることを知っています.loop()においても、以降のタスクに対する処理も同じスレッドにあり、HandlerMessage()がスレッド切替処理を実行していないこと、すなわちloop()メソッドを呼び出すLooperインスタンスがどのスレッドで作成され、どのスレッドで実行されるかをプロセス全体で知るとともに、ホログラムキューmQueueも同じスレッドにあり、実行スレッドがHandlerによって作成されるスレッドであると言われるのは、主スレッドまたはサブスレッドが無パラメトリック関数を使用してHandler()を作成する場合、LooperインスタンスとHandlerインスタンスは同じスレッドにあるため、タスクとHandlerはこのスレッドを実行し、Handlerが実際に実行するスレッドはメッセージキューLooper()インスタンスのあるスレッドであると結論することができる.この結論の検証については、コードは簡単です.読者に自分で検証してください.