This article is good for you to know something about Handler, even if you forget it. ~~ but to the online <<loop circle graph >> a little impression of the player.

Concept of pre –


Synchronous barrier messages

  1. Function: A special message used by the system, which can be seen as a priority for processing asynchronous messages, when the queue head of a MessageQueue isSynchronizing barrier messages, ignoring synchronous messages and executing the most recent asynchronous messages. throughpostSyncBarrier/removeSyncBarrierAdd and delete synchronization barrier messages are not automatically removed unless manually removed.
  2. Features:targetProperty is emptyMessageSynchronous barrier messages
  3. Example:ViewRootImpl.scheduleTraversalsProcess asynchronous messages first

IdleHandler

  1. Function: idle timeHandler, an operation that is performed when there is no message or when a message is not triggered.
  2. Features:MessageQueueStatic interface, use when overwriteboolean queueIdle()The method performs an idle operation and returns a value indicating whether it remains alive after execution.

epoll

Linux IO mode and select, poll, epoll details

The body of the


Let’s talk about the fundamentals

A consumer enters a message to a message queue in a Looper in TLS of the target thread through a method exposed outside of Handler.

Message queues retrieve messages in a timely/delayed manner and distribute them for processing. To achieve scheduled or delayed operation.

Handler by MessageQueue. EnqueueMessage (MSG, when) enqueued message

Looper.loop sends queue messages through messagequeue.next ()

MessageQueue

MessageQueue key variable mMessages:

An instance of a message queue that queues messages according to their firing timing. The code is represented as a single linked list node, representing a queue header (linked header) message.

Graph 1 [A delay: 1 s] - LR next - > 2 [B delay: 2 s] -- next - > 3 [C delay: 3 s] - next - > 5 [D latency: 5 s]

Team to

  1. The method of entranceenqueueMessage(), stores delayed triggering messages to the queue and queues them according to the triggering time.
  2. A team approachnext()The queue is iterated in an infinite loop, and messages that reach the trigger time are fetched.

Block/sleep:

How can we delay the initiation of the team?

Block the next() method so that it cannot fetch messages. When the time is up, the block is recovered and the message can be retrieved.Copy the code

There is no message in the queue, and the queue exit method keeps fetching messages in an infinite loop.

Block the next() method without a message, preventing it from fetching messages. When a new message is inserted, tell him to pick it up.Copy the code
The next message in the queue is still a long way off: block first. The team has no message at all: it sleeps until there is one.

The specific practices

Next () ‘s message fetch loop is blocked/hibernated with nativePollOnce(PTR, nextPollTimeoutMillis).

  • Blocks until the trigger time of the message is not arrived;
  • If there is no message in the queue, sleep until a new message enters the queueenqueueMessage()Within thenativeWake(mPtr)Wake up.

Expansion: You can’t

The timeout passed in by nativePollOnce passes JNI to the Native layer Looper::pollOnce->Looper::pollInner ->epoll_wait method.

Epoll_wait uses the file descriptor A created by epoll_create to listen for the pipeline to read the end file descriptor B (added using epoll_ctl).

  • When timeout>0, the listening duration exceeds this valuetimeoutStill no event is returned, interrupting the blocking.
  • Timeout = 1,epoll_waitWait until a new message comes inenqueueMessage()Within thenativeWake(mPtr)Write “W” to the writing end of the pipe in the Native layer to trigger the listening interrupt blocking. Empty pipe data at the same time.

In both cases, a result is returned, and pollOnce exits when it receives either result.

The epoll I/O reuse mechanism uses a single file descriptor to listen for events from multiple file descriptors.

Out of the team

NativePollOnce (PTR, nextPollTimeoutMillis) method parameter nextPollTimeoutMillis (that is, the delay time for the next message) value.

Latency for the next message In message queue jams
> 0 Delay the latest message, trigger time is not reached Block until trigger time Releasing CPU Resources
= 0 Delay the latest message. Trigger time is up Don’t block
=-1 There’s no news. Sleep until there is a message Releasing CPU Resources

Interpretation process

Next () exits the queue, requiring a Message return value. When AtivePollOnce no longer blocks, because the queue is sorted by trigger time:

  1. Usually take the first queue message;

  2. However, if the queue head is synchronous barrier message [Barrier1], the nearest asynchronous message should be taken.

So we take the MSG, either the queue head or the most recent asynchronous, and then decide whether we should return it and other subsequent actions.

  • When MSG is not empty [2]

    1. If MSG trigger time reaches [3 ‘], return MSG. (Of course, tidy up the queue before returning)
    2. If MSG trigger time is not reached [3], the trigger time is recalculated, and then nextPollTimeoutMillis is set to the new time, and the operation of whether there is IdleHandler and its processing is performed as in the following “when MSG is empty”. 【4】/【 5,6 】
  • When MSG is empty [2 ‘], first set nextPollTimeoutMillis to -1

    1. If there is no IdleHandler [4] to be processed, then the loop is broken and returnednativePollOnceAt this time,nextPollTimeoutMillis=-1Block until a new message wakes it up.
    2. If there are idleHandlers to be processed: execute these idleHandlers [5] through (up to four at a time, execute themqueueIdleCallback) and then reset the IdleHandler count andnextPollTimeoutMillis=0Complete the loop [6] (nextPollTimeoutMillis=0Let the next loop stop blocking to check if a new message is queued while processing IdleHandler.
Message next(a) {
    final long ptr = mPtr;/*MessageQueue native layer address */
    if (ptr == 0) {// When the message loop has exited, it returns directly
        return null;
    }
    int pendingIdleHandlerCount = -1; // Number of handlers in idle time
    int nextPollTimeoutMillis = 0;
    for (;;) {
        nativePollOnce(ptr, nextPollTimeoutMillis);// [1] Block: object.wait()
        synchronized (this) {
            final long now = SystemClock.uptimeMillis();
            Message prevMsg = null;
            Message msg = mMessages;/* Next () returns a header message */
            if(msg ! =null && msg.target == null) {// if the queue header is a synchronous barrier message, MSG takes the nearest asynchronous message
		do {
                   prevMsg = msg;
                   msg = msg.next;
                } while(msg ! =null && !msg.isAsynchronous());// If MSG is not asynchronous, each message is traversed from the head of the queue to the end of the queue until the MSG is asynchronous
            }            
            if(msg ! =null) {[2] fetch the MSG to be processed
                if (now < msg.when) {/* [3] Update delay */
                    nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                } else {/* [3 '] the time to process MSG is up: fetch MSG and dequeue */
                    mBlocked = false;/* Blocked or not: Set to false for storing messages with */
                    if(prevMsg ! =null) {/* [Barrier1 '] if MSG is the latest asynchronous message, change the pointer to skip MSG */
                        prevMsg.next = msg.next;
                    } else {/* retrieve MSG and update the next message as queue head */
                        mMessages = msg.next;
                    }
                    msg.next = null;// About to be returned, next becomes meaningless and empty.
                    return msg;/* Returns the next message */}}else {/* [2 '] no message is sent */
                nextPollTimeoutMillis = -1;NextPollTimeoutMillis set to -1. Thread blocked */
            }
            
            / * -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- free handler processing -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - * /
            /* Idlehandles runs only if the queue is empty or if the queue header message is not ready
            if (pendingIdleHandlerCount < 0
                    && (mMessages == null || now < mMessages.when)) {
                pendingIdleHandlerCount = mIdleHandlers.size();/* Count the number of idle tasks */
            }
            if (pendingIdleHandlerCount <= 0) {
                mBlocked = true;/* * * * * * * * * * * * * * * * * * * *
                continue;
            }
            if (mPendingIdleHandlers == null) {/* The last if will continue */
                mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
            }
            mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
        }
        /* [5] The Handler needs to iterate through the execution when it is free. Continue if there is no idle Handler. * /
        for (int i = 0; i < pendingIdleHandlerCount; i++) {
            final IdleHandler idler = mPendingIdleHandlers[i];
                // [5.1] Execute the queueIdle method of IdleHandler, run IdelHandler, such as log reporting Gc, and decide whether to keep the alive state by returning the value
               idler.queueIdle();
        }
	/* [6] The Handler resets the idle count and the next delay */
        pendingIdleHandlerCount = 0;
        // There may be a new message enqueue in the process of executing the idle Handler(step [5] is not synchronized) and it needs to be checked again.
        // Set the next delay time to 0, and the next cycle to step [1] will not block.
        nextPollTimeoutMillis = 0; }}Copy the code

Sequence diagram

The team

Whether the enqueued method next() is blocked at pollOnce() (nextPollTimeoutMillis≠0). The purpose of recording whether pollOnce is blocked is to see if it needs to be woken up

External exposure operation method of the Handler class, send (empty) Message/post + atTime/delay/AtFrontOfQueue operations such as the final destination. EnqueueMessage (Message MSG, long WHEN) : Adds MSG to queue. When is the non-sleep running time (milliseconds) since system startup.

Interpretation process

To enqueue a message, the process refers to the situation diagram of the stored message and compares it to the code below.

Situation [I]

The queue is empty, the new message is an instant message, and the new message is the shortest delay message

New enqueued messages are inserted into the header of the queue: nativeWake is required to wake up the pollonce of the queue

A. There is no message in the queue B. The message delay for new entrants is 0 C. New messages are triggered before the leader of the queue. Similar to b
  1. Insert the message by changing the next pointer to the first message and the new message.
  2. Do I need to wake up?needWake = mBlockedIt’s time to get out of linepollonceplaceThe queue has no message yetorThe timing of recent news has not been forthcomingAnd wasblocking.mBlocked=trueIt’s inevitable. thennativeWakeTo wake uppollonceTo retrieve the message you just saved.

Situation 【 II 】

New messages are not inserted at the head of the queue, but in the middle of the queue. Find the location before inserting.

The team head is synchronous barrier messages And the inserted message is the most recent asynchronous message The inserted message is not the most recent asynchronous message
Do I need to wake up? Need to wake up No need to wake up

Unless the queue header is a synchronous barrier message and the inserted message is the most recent asynchronous message, most other cases inserted into the middle of the queue do not need to be woken up

NeedWake = mBlocked && p.target == null && msg.isasynchronous ();

  1. Out of the teampollonceThe queue is not empty after the last if and is blocked.mBlocked=trueIt’s inevitable.
  2. p.target == null The target of queue leader P is null, consistent with synchronous barrier messages.
  3. msg.isAsynchronous()A newly inserted message in the middle of the queue is an asynchronous message.

The combined wake-up condition is: “An asynchronous message was inserted when the last message in the queue was not triggered and the first message in the queue was a synchronous barrier message” (which may change).

Then, find the appropriate insertion position by changing the next pointer to traverse from the front to the back of the queue:

  1. (when < p.hen)That is, the trigger time of the new message is earlier than the trigger time of the position, the insertion position is found, and the traversal is jumped.
  2. p == nullAt the end of the traversal, the trigger time of the new message is later than the message in the team, and the insertion position is at the end of the team, and the traversal jumps out.
  3. In the process of finding the insertion location. If an asynchronous message is found, the new message is asynchronous but not closest to the trigger and does not need to be woken up. So the wake up condition is updated to: when the queue head is a synchronous barrier message, the newly inserted message is the asynchronous message closest to the trigger.

Finally, the pointer is changed to insert the message in place.

boolean enqueueMessage(Message msg, long when) {    
    synchronized (this) {/* There may be multiple threads sending messages */
        msg.when = when;
        Message p = mMessages;// p is assigned to the queue head. Sort by when
        boolean needWake;
        if (p == null || when == 0 || when < p.when) {
          When the queue is empty, the delay of new messages is 0, which is an instant message. 3. The delay of new messages is shorter than that of the first queue
            msg.next = p;
            mMessages = msg;
            needWake = mBlocked;PollOnce () (nextPollTimeoutMillis≠0) */
        } else {[b] The message is inserted in the middle of the MessageQueue, usually without waking up the thread. Unless the team leader synchronizes the barrier and MSG is! Recently! Asynchronous messaging
            // the queue header is synchronous barrier message, and the inserted MSG is asynchronous message.
            needWake = mBlocked && p.target == null && msg.isAsynchronous();
            Message prev;
            for (;;) {
                prev = p;
                p = p.next;
                /*prev, p (0, 1), p (last, null) */
                if (p == null /*last.next=null inserted at the end */|| when < p.when/* (queue p.hen is getting bigger and bigger 1235, when=4) */
                    break;
                }
                if (needWake && p.isAsynchronous()) {// select * from MSG
                    // An asynchronous message was found while looking for the MSG insertion location. MSG is preceded by an earlier asynchronous message. MSG is asynchronous, but not recent. Don't need to wake up
                     needWake = false; }}/* Insert the queue MSG between prev and p (3-5) */
            msg.next = p; 
            prev.next = msg;
        }
        if (needWake) {
            nativeWake(mPtr);//【三】 wake up thread, nativePollOnce is not blocked}}return true;
}
Copy the code

The flow chart