Android漫談——Handler原理(一):デッドロックコード、postDelay()実現原理.

33840 ワード

プリシーケンス
疫病がひどくて、家で暇で退屈で、たくさんのブログを見ました.読むブログが多くなるにつれて、自分ができないことが多くなると同時に、忘れることも多くなり、うとうとしていることも多くなり、前に覚えていたことや理解していたことが多くなり、今では覚えられなくなり、理解できなくなったので、今回はソースをもう一度めくって、よく読んで、この文章が誕生しました.また、前にソースコードを见て、多くは各ブログについて行って、彼らの分析が终わって、私も自分が理解したと思って、実ははるかにそうではありませんて、その上、私达は普通ソースコードを见て、すべてとても强い目的性を持って、だから多くのものを见落として、しかし、知识の蓄积が十分になって、更に见て、実は多くのソースコード、とても面白いです.ソースコードを見ると、細部にこだわる必要はありませんが、特に重要な知識点は、よくつかんでみると、意外なことがたくさんあります.そして、私はそのような一反三の文章が好きで、一つの知識点といえば、別の知識点に触れることができて、そばに参考リンクをあげて、主線は変わらないで、この主線から、他の問題と知識を引き起こして、とても面白いです.スレ主は一連のテーマブログを欲しがっています.重点は強調して、他の人の言うこと、自分で理解したのと、同じではありません.しかし、スレ主は長文の文章に反感を持っています.一つは読者が類を見ていることです.二つ目は、自分が将来調べるのも不便です.だから、短くしてください.あまり話さないで、これから始めましょう.
本題
Handler主線
まず、この文章を読むには少し基礎が必要で、みんなが知っているHandlerの基礎知識を話すことはできません.せいぜい大体過ぎて、注意して、それから、前に無視した、分析していない、しかも金を含むコードを深くつかむだけです.このブログの目的は、ある知識点を説明するだけでなく、すべての関連知識点を説明することです.
今回の分析では,主にHandlerがsendMessageを主線とし,他のMessageに関する知識点を横取りした.まず、すべてのHandler.sendMessageは、次のコードに進みます(重点コードを保持し、自分のコメントを加えて、無視しないで、見てください).
MessageQueue.java
boolean enqueueMessage(Message msg, long when) {
		//             。
        synchronized (this) {
        //mQuitting,    。
            if (mQuitting) {
                IllegalStateException e = new IllegalStateException(
                        msg.target + " sending message to a Handler on a dead thread");
                Log.w(TAG, e.getMessage(), e);
                msg.recycle();
                return false;
            }
			//markInUse,         
            msg.markInUse();
            msg.when = when;
            Message p = mMessages;
            boolean needWake;
            //p==null           message
            //    when==0||when
            if (p == null || when == 0 || when < p.when) {
				//    ,              。
                // New head, wake up the event queue if blocked.
                msg.next = p;
                mMessages = msg;
                //mBlocked   ,       。
                needWake = mBlocked;
            } else {
            //             ,              ,                。
                // Inserted within the middle of the queue.  Usually we don't have to wake
                // up the event queue unless there is a barrier at the head of the queue
                // and the message is the earliest asynchronous message in the queue.
                //    ,       Handler      :         。
                needWake = mBlocked && p.target == null && msg.isAsynchronous();
                Message prev;
                for (;;) {
                //     ,      ,    msg       
                    prev = p;
                    p = p.next;
                    if (p == null || when < p.when) {
                        break;
                    }
                    //        ,               ,      ,    。
                    if (needWake && p.isAsynchronous()) {
                        needWake = false;
                    }
                }
                //    msg        ,            ,          。
                msg.next = p; // invariant: p == prev.next
                prev.next = msg;
            }
			//      ,       ,       ,nativeWake      。           ,               。
            // We can assume mPtr != 0 because mQuitting is false.
            if (needWake) {
                nativeWake(mPtr);
            }
        }
        return true;
}

このコードはみんなが見たことがあると信じています.以下、いくつかの疑問点について、一つ一つ説明します.まず、needWakeとnativeWake(mPtr)というコードです.まずneedWakeです.名前の通り、needWakeは、誰を呼び覚ます必要がありますか?もちろんメッセージキューを起動しますが、なぜメッセージキューを起動する必要がありますか?メッセージキューがnativePollOnceによってブロックされたためです.私たちはしばらくC層コードを分析しないで、C層コードが何をしているのかを覚えておけばいいです(さもないとepollとpipeメカニズムを分析するのは面倒です.)
needWake = mBlocked && p.target == null && msg.isAsynchronous();

このコードは、p.target==null&&msg.isAsynchronous();メッセージバリアや非同期メッセージについては、後で時間をかけて説明し、自分でネットで見てもいいので、よく理解できます.しかし、次の内容は、メッセージバリアについて理解していることを保証する必要があります.そしてmBlockedですが、このmBlockedには2つの付与場所があり、next()メソッドの中にあります.この方法はとても長くて、関連する知識点も比較的に多くて、しばらく先に私たちの次の分析の一部を貼り出します.
for (;;) {
	nativePollOnce(ptr, nextPollTimeoutMillis);
	synchronized (this) {
				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;
                        //           msg       ,     ,         。
                        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;
                }

                // Process the quit message now that all pending messages have been handled.
                if (mQuitting) {
                    dispose();
                    return null;
                }
	}
}

nativePollOnce()という関数は興味深い.ptrとnextPollTimeoutMillisの2つのパラメータが伝わっている.ここでは、上述のepollとpipeメカニズムである.現在メッセージ処理がない場合は、ここでブロックされます.メッセージが遅延メッセージである場合、nextPollTimeoutMillis時間が経過すると回復します.ここはLinuxの底に触れて、スレ主も特に理解していないので、後で私が補充します.ここではまずメッセージバリアと非同期メッセージを省略し、後でブログをまとめるのに時間がかかります.まとめた後、私はここでアドレスを提供します.今、後ろの論理を直接見ます.このコードを見てみましょう
                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;
                }

明らかに、このコードは適切なmsgを見つけて戻って、コードを探す過程で、msgがすぐに処理する必要があることを説明して、だからmBlockedはfalseに置いて、現在ブロックしていません.残りのnext()コードは、一部を選択して貼り付けます

                // If first time idle, then get the number of idlers to run.
                // Idle handles only run if the queue is empty or if the first message
                // in the queue (possibly a barrier) is due to be handled in the future.
                if (pendingIdleHandlerCount < 0
                        && (mMessages == null || now < mMessages.when)) {
                    pendingIdleHandlerCount = mIdleHandlers.size();
                }
                if (pendingIdleHandlerCount <= 0) {
                    // No idle handlers to run.  Loop and wait some more.
                    //    ,             ,         。
                    mBlocked = true;
                    continue;
                }

                if (mPendingIdleHandlers == null) {
                    mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
                }
                mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);

これはIdleHandlerに関しても興味深いコードであり、以降のブログでは専門的に分析されている(ちなみに面接で必ず聞かれるLeakCanaryはIdleHandlerで処理されている).もう1箇所mBlocked=trueはすでに注釈の場所で説明されています.ここまで行けるのは、msgが見つからないに違いない.つまり、msgが処理する必要がないので、ブロック状態にある.では、この2つのmBlockedの分析が終わり、まとめてみましょう.mBlockedは、キューに処理する必要があるメッセージがあるかどうか、つまりブロックされているかどうかを判断するために使用されます.はい、上で分析したコードを見てみましょう.
needWake = mBlocked && p.target == null && msg.isAsynchronous();

すなわち、1.ブロックされた状態にあり、現在のメッセージ(現在挿入されるメッセージであることに注意)がメッセージバリアと非同期メッセージの条件を満たす場合(メッセージが満たされるのは何ですか?たとえば、Viewを描画すると、ViewRootImpl.scheduleTraversals()にこのようなバリアが送信されます)、needWakeはtrueです.そうしないと、いずれも満たされないのはfalseです.その後のforサイクルでは、付与コードもあります.
                Message prev;
                for (;;) {
                    prev = p;
                    p = p.next;
                    if (p == null || when < p.when) {
                        break;
                    }
                    //     true          ,    false。
                    //     ,       msg       p     ,                    ,
                    if (needWake && p.isAsynchronous()) {
                        needWake = false;
                    }
                }

ここで、上記の解析と併せて、現在ブロック状態にあり、メッセージバリアがあり、挿入されるメッセージが非同期メッセージであり、挿入されるメッセージの時点より前に非同期メッセージがある場合、起動しない.大変ですね~では、なぜ目を覚まさないのですか?非同期メッセージには何か特別なものがありますか?前のnext()コードをもう一度戻ってみるしかありません.ここにはもう貼らない.もう一度考えて、まとめてみると、実はあなたは知っています.キューがblockedがtrueの状態でなければ、自然に呼び覚ます必要はありません(もともと目が覚めているので、そんなに手間がかかる必要はありません)、blockedがfalseの場合だけ、呼び覚ます必要がありますので、どのような場合、呼び覚ます必要があるかを分析する必要があります.すなわち、mBlockedがtrueであり、同期バリアがある後に追加される最初の非同期メッセージは、起動する必要がある(時間がなくても起動する必要がある).どうしてですか.これは同期バリアの定義に関連する.同期バリアがある場合、非同期メッセージしか処理できません(同期バリアは、もちろん同期メッセージをブロックし、非同期メッセージを流します).したがって、ここではnativePollOnceをリセットするために待機時間(すなわちnextPollTimeoutMillis)が必要であり、これまでの待機時間は同期メッセージの待機時間であったためである.具体的なコードは以下のように整理されています.
				...
				nativePollOnce(ptr, nextPollTimeoutMillis);
				...
                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 {
                        ...
                    }
                } else {
					...
                }
				...
                if (        ) {
                    // No idle handlers to run.  Loop and wait some more.
                    mBlocked = true;
                    //continue     nativePollOnce()
                    continue;
                }

ここまで来ると、ほぼ全部出てきます.私たちはすべての状況をまとめると、もっとはっきり見えます.
まとめ
新しいmsg(enqueueMessage()を追加するたびに、pipeがブロックしているキューを起動する必要があるかどうかを判断します.
  • 挿入されるmsgが直ちに処理(遅延なし)を必要とし、キューがブロック状態(mBlocked=true)である場合、起動する.
  • 挿入するmsgがすぐに処理する必要がない場合(遅延がある場合)は、いくつかの状況に分けられます.
  • 通常メッセージまたは非同期メッセージの場合、前面にバリアがなく、起動しません.
  • 非同期メッセージであり、現在のキューヘッダがバリアである場合は、次の2つのケースに分けられます.
  • は、最初のメッセージであれば、メッセージを処理するのではなく、 nativePollOnce ( nextPollTimeoutMillis)のために起動する.
  • 最初のメッセージでなければ、起動しません.



  • まとめましたが、これは今まで見た中で最も詳細なpostDelay実現原理であるはずです.コアは nativePollOnce ( nextPollTimeoutMillis)で、これを理解して、基本的にすべての分析を理解しました.
    に続く
    この文章には多くの穴が残っているので,後で補う必要がある.実はHandlerがデザインしたものが多いです.例えば、UIはサブスレッドでしか更新できませんか?View.post(Runnable)はなぜ有効にならないことが多いのでしょうか.これは、もう一つの知識点にかかわるので、後でゆっくり補充します.