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
- 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. through
postSyncBarrier
/removeSyncBarrier
Add and delete synchronization barrier messages are not automatically removed unless manually removed. - Features:
target
Property is emptyMessage
Synchronous barrier messages - Example:
ViewRootImpl.scheduleTraversals
Process asynchronous messages first
IdleHandler
- Function: idle time
Handler
, an operation that is performed when there is no message or when a message is not triggered. - Features:
MessageQueue
Static 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
- The method of entrance
enqueueMessage()
, stores delayed triggering messages to the queue and queues them according to the triggering time. - A team approach
next()
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 queue
enqueueMessage()
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 value
timeout
Still no event is returned, interrupting the blocking.- Timeout = 1,
epoll_wait
Wait 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:
-
Usually take the first queue message;
-
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]
- If MSG trigger time reaches [3 ‘], return MSG. (Of course, tidy up the queue before returning)
- 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
- If there is no IdleHandler [4] to be processed, then the loop is broken and returned
nativePollOnce
At this time,nextPollTimeoutMillis=-1
Block until a new message wakes it up. - If there are idleHandlers to be processed: execute these idleHandlers [5] through (up to four at a time, execute them
queueIdle
Callback) and then reset the IdleHandler count andnextPollTimeoutMillis=0
Complete the loop [6] (nextPollTimeoutMillis=0
Let the next loop stop blocking to check if a new message is queued while processing IdleHandler.
- If there is no IdleHandler [4] to be processed, then the loop is broken and returned
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 |
---|---|---|
- Insert the message by changing the next pointer to the first message and the new message.
- Do I need to wake up?
needWake = mBlocked
It’s time to get out of linepollonce
placeThe queue has no message yetorThe timing of recent news has not been forthcomingAnd wasblocking.mBlocked=trueIt’s inevitable. thennativeWake
To wake uppollonce
To 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 ();
- Out of the team
pollonce
The queue is not empty after the last if and is blocked.mBlocked=trueIt’s inevitable. p.target == null
The target of queue leader P is null, consistent with synchronous barrier messages.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:
(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.p == null
At 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.- 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