Androidソース分析-Handlerの同期バリアメカニズム

5491 ワード

Handlerのメカニズムに詳しいと思っていたが、最近、洋神のwanAndroidでHandler に関する質問を見て、これは確かに知識の盲点であることを認めざるを得なかった.そこでちょうど暇な時間に簡単に研究してみましたが、その原理を簡単に分析します.本文の参考資料:
  • 毎日Handler問答Handlerはみんながもっとよく知っているクラスであるべきで、その中に同期バリアのメカニズムがあって、あなたはどのくらい知っていますか?
  • Handlerの同期バリア機構(sync barrier)
  • 1.Handlerの非同期メッセージ
      同期バリアを紹介するとき,まずHandlerの非同期メッセージを見てみよう.通常、Handlerを使用してタスクキューに追加したMessageは同期されています.非同期のMessageを追加したい場合は、次の2つの方法で行います.
  • Handlerの構造方法にはasyncのパラメータがあり、new Handlerのときにtrueと伝えればよいが、その後このHandlerを用いて追加されたMessageは非同期である.
  •     private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
            msg.target = this;
            if (mAsynchronous) {
                msg.setAsynchronous(true);
            }
            return queue.enqueueMessage(msg, uptimeMillis);
        }
    
  • は、Messageオブジェクトを作成するときに、MessagesetAsynchronousメソッドを直接呼び出す.
  •   一般的には、非同期メッセージと同期には差はありませんが、同期バリアがオンになると差があります.
    2.同期バリア
    一般に、MessageQueueは、一定の順序で厳格に実行される.しかし、この時、高優の任務が実行される必要がある場合は、どうすればいいのでしょうか.Messageの実行時間を直接現在時間に設定すればいいという人もいますが、キューに似たようなMessageが複数ある場合、Messageが次回実行できる保証はありません(優れたタスクでも、実行中のタスクを中断することはできません.ジルコニアこの場合、同期バリアが必要です.優れたタスクを実行する必要がある場合は、キュー内の同期メッセージの実行を阻害するために、タスクキューに同期バリアを挿入できます.同期バリアの本質は、現在のLooperがタスクキューから同期バリアを取り出した後、タスクキューから最初の非同期メッセージを取り出して実行することである.同期バリアと非同期メッセージの組み合わせにより、メッセージが直ちに実行される目的を達成することができる.どうやって同期バリアを開きますか?MessageQueuepostSyncBarrierメソッドを呼び出して開くことができ、そのコードも非常に簡単です.
        private int postSyncBarrier(long when) {
            // Enqueue a new sync barrier token.
            // We don't need to wake the queue because the purpose of a barrier is to stall it.
            synchronized (this) {
                final int token = mNextBarrierToken++;
                final Message msg = Message.obtain();
                msg.markInUse();
                msg.when = when;
                msg.arg1 = token;
    
                Message prev = null;
                Message p = mMessages;
                if (when != 0) {
                    while (p != null && p.when <= when) {
                        prev = p;
                        p = p.next;
                    }
                }
                if (prev != null) { // invariant: p == prev.next
                    msg.next = p;
                    prev.next = msg;
                } else {
                    msg.next = p;
                    mMessages = msg;
                }
                return token;
            }
        }
    
      postSyncBarrierメソッドが呼び出されると、同期バリアであるMessageを表すtarget nullのMessageがタスクキューに挿入されます.
    3.非同期メッセージの処理
      同期バリアを開き、タスクキューに非同期メッセージをpostした後、Looperがポーリング中に同期バリアに遭遇すると、タスクキュー内で非同期メッセージが見つかります.具体的なコードを見てみましょう.
                    final long now = SystemClock.uptimeMillis();
                    Message prevMsg = null;
                    Message msg = mMessages;
                    if (msg != null && msg.target == null) {
                        // Stalled by a barrier.  Find the next asynchronous message in the queue.
                        do {
                            prevMsg = msg;
                            msg = msg.next;
                        } while (msg != null && !msg.isAsynchronous());
                    }
                    if (msg != null) {
                        if (now < msg.when) {
                            // Next message is not ready.  Set a timeout to wake up when it is ready.
                            nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                        } else {
                            // Got a message.
                            mBlocked = false;
                            if (prevMsg != null) {
                                prevMsg.next = msg.next;
                            } else {
                                mMessages = msg.next;
                            }
                            msg.next = null;
                            if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                            msg.markInUse();
                            return msg;
                        }
                    } else {
                        // No more messages.
                        nextPollTimeoutMillis = -1;
                    }
    
      上のコードから、MessageQueueがtargetがnullのMessageを取得すると、後で非同期メッセージが見つかり、実行されることを知っています.もちろん、非同期メッセージが取得されない場合、nextPollTimeoutMillisは−1に設定され、非同期メッセージが1つあるまでキューがスリープされる.
    4.応用
    同期バリアの原理を理解しましたが、何の役割がありますか?また、注意深い学生は、Api 28バージョンではpostSyncBarrierメソッドがhideと表記されていることを発見するはずです.つまり、グーグルのお父さんは私たちに使用することをお勧めしません.しかし、ViewRootImplscheduleTraversalsメソッドでは、システムのソースコードから関連するアプリケーションを見つけることもできます.
        void scheduleTraversals() {
            if (!mTraversalScheduled) {
                mTraversalScheduled = true;
                mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
                mChoreographer.postCallback(
                        Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
                if (!mUnbufferedInputDispatch) {
                    scheduleConsumeBatchedInput();
                }
                notifyRendererOfFramePending();
                pokeDrawLockIfNeeded();
            }
        }
    
    前にこの部分のコードを見たことがあるかもしれませんが、なぜこのようにPost Messageに来たのか分からないかもしれません.同期バリアを理解した後,3つのプロセスが直ちに実行できるようにしたことが分かった.これにより、すぐに実行するタスクがある場合や、Viewをクリックしてすぐに応答する必要がある場合は、同期バリアを通じて実行することができます.現在postSyncBarrierメソッドはhideと表記されているが、オプションのメソッドも少なくない.