This is the fourth day of my participation in the August More text Challenge. For details, see: August More Text Challenge
Familiar yet unfamiliar handler-2
Handler-1 is both familiar and unfamiliar
How does an enqueueMessage message enter a queue
Handler sends a message, which ultimately puts the message on a message queue:
boolean enqueueMessage(Message msg, long when) {
// Still do some necessary checks
if (msg.target == null) {
throw new IllegalArgumentException("Message must have a target.");
}
if (msg.isInUse()) {
throw new IllegalStateException(msg + " This message is already in use.");
}
// Lock yourself
synchronized (this) {
// If the queue is in the exit state, add a message to it.
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;
}
// Indicates that the current Message is in use.
msg.markInUse();
// When is the time that the previously passed message is expected to be processed, packaged as a message object
msg.when = when;
Message p = mMessages;
boolean needWake;
// caseA: there is no message in the queue, or the message is expected to be processed at the earliest time, then the current inserted message is the queue header
/ / Handler sendMessageAtFrontOfQueue this API is through the when = 0,
// to insert a message into the header of the message queue
if (p == null || when == 0 || when < p.when) {
// New head, wake up the event queue if blocked.
msg.next = p;
mMessages = msg;
// If the queue is suspended, it needs to be woken up
needWake = mBlocked;
} else {
// caseB: there are messages in the queue
// Do you need to wake up the queue?
// The queue is in the pending state && the header is a synchronous barrier message (target is null) && The new message is an asynchronous message
// This is because asynchronous messages can pass through the synchronization barrier if the queue is suspended
// You need to wake up the queue to ensure that the asynchronous message can be retrieved
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
// Traverse the entire message queue
for (;;) {
prev = p;
p = p.next;
// The end of the Message queue is reached, or the newly queued Message is expected to be processed earlier than the Message being traversed
// Then the corresponding position is found
if (p == null || when < p.when) {
break;
}
// There is no need to wake up if the new message is already preceded by an asynchronous message
// This is because the message queue is already running
if (needWake && p.isAsynchronous()) {
needWake = false; }}// Insert queue
msg.next = p; // invariant: p == prev.next
prev.next = msg;
}
// If you need to wake up, call nativeWake to wake up the message queue. The actual implementation will be analyzed later
if(needWake) { nativeWake(mPtr); }}return true;
}
Copy the code
SyncBarrier Synchronization barrier
In the above analysis, the concept of synchronization barrier is touched. The so-called synchronization barrier is a special message. When a message queue encounters a synchronization barrier, asynchronous messages are preferentially processed and synchronous messages are masked.
In viewrootimpl.java, there is a line that calls:
void scheduleTraversals(a) {
if(! mTraversalScheduled) { mTraversalScheduled =true;
// Send a sync barrier to MainLooper
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); notifyRendererOfFramePending(); pokeDrawLockIfNeeded(); }}Copy the code
PostSyncBarrier is implemented as follows:
private int postSyncBarrier(long when) {
synchronized (this) {
// Token Indicates the synchronization barrier identifier
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) {
// Traverse the message queue to find where the current synchronization barrier should be inserted
while(p ! =null&& p.when <= when) { prev = p; p = p.next; }}// If you can't find where to insert, then the synchronization barrier is the queue head
if(prev ! =null) { // invariant: p == prev.next
msg.next = p;
prev.next = msg;
} else {
// Insert at the specified location
msg.next = p;
mMessages = msg;
}
returntoken; }}Copy the code
There is an API for sending synchronization barriers, and there is also an API for removing them (of course, they are not allowed to be called by an App). RemoveSyncBarrier:
public void removeSyncBarrier(int token) {
synchronized (this) {
Message prev = null;
Message p = mMessages;
// Based on the token, find the synchronization barrier to be removed
while(p ! =null&& (p.target ! =null|| p.arg1 ! = token)) { prev = p; p = p.next; }// No synchronization barrier was found (no synchronization barrier added or removed) throw an exception
if (p == null) {
throw new IllegalStateException("The specified message queue synchronization "
+ " barrier token has not been posted or has already been removed.");
}
/ / needWake to false
final boolean needWake;
// If the synchronization barrier is in the middle of the queue, remove the synchronization barrier. The queue is already looping
// Do not need to perform the wake up operation
if(prev ! =null) {
prev.next = p.next;
needWake = false;
} else {
// If the synchronization barrier is at the head of the queue, point the queue header to the next message in the synchronization barrier
mMessages = p.next;
// After the queue header synchronization barrier is removed
// Wake up the message loop if the message queue is empty or if the next message is not a synchronization barrier
needWake = mMessages == null|| mMessages.target ! =null;
}
p.recycleUnchecked();
// If you need to wake up the message loop, call nativeWake
if(needWake && ! mQuitting) { nativeWake(mPtr); }}}Copy the code
How does the next message get out of the queue
After analyzing how the message enters the queue, let’s take a look at how the message in the queue is retrieved. In the above analysis of looper. loop, we know that in loop method, a message is retrieved by calling next:
Message next(a) {
// mPtr=0 indicates that the message queue has been dequeued and no messages need to be processed
final long ptr = mPtr;
if (ptr == 0) {
return null;
}
// Identifies how many IdleHandlers need to be processed
int pendingIdleHandlerCount = -1; // -1 only during first iteration
// The amount of time to wait before the next message arrives
int nextPollTimeoutMillis = 0;
for (;;) {
if(nextPollTimeoutMillis ! =0) {
Binder.flushPendingCommands();
}
// Suspend the operation, the underlying implementation, later to do the analysis
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
// For target==null, the corresponding message is a synchronization barrier
// When a synchronization barrier is encountered
if(msg ! =null && msg.target == null) {
// Fetch the first asynchronous message and assign it to the MSG field
do {
prevMsg = msg;
msg = msg.next;
} while(msg ! =null && !msg.isAsynchronous());
}
if(msg ! =null) {
// If the current timestamp is less than the expected processing time of the message being processed
// This indicates that the queue should not fetch the message yet
if (now < msg.when) {
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// This indicates that a message has been retrieved that can be processed
// Mark the pending status of the queue as false
mBlocked = false;
// Update the queue header
if(prevMsg ! =null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
// Pinch the next field of the fetched message
msg.next = null;
// Indicates that the Message is being used and returns the Message object for the loop to distribute
msg.markInUse();
returnmsg; }}else {
// If there are no messages in the queue, you may need to wait
nextPollTimeoutMillis = -1;
}
// Return null if the queue is pushing out
if (mQuitting) {
dispose();
return null;
}
// Next, enter the IdleHandler time, when the message queue is idle
// Will consume these IdleHandlers.
PendingIdleHandlerCount starts with -1
// The second condition indicates that there is no message or that the message queue is idle
if (pendingIdleHandlerCount < 0
&& (mMessages == null || now < mMessages.when)) {
How many IdleHandlers need to be processed from ArrayList
pendingIdleHandlerCount = mIdleHandlers.size();
}
// If there is no idleHandler, then the queue really can rest and hang
if (pendingIdleHandlerCount <= 0) {
mBlocked = true;
continue;
}
// Initialize the mPendingIdleHandlers field with a maximum size of 4
if (mPendingIdleHandlers == null) {
mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
}
mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
}
// Start handling idleHandlers
for (int i = 0; i < pendingIdleHandlerCount; i++) {
// Retrieve the idleHandlers one at a time
final IdleHandler idler = mPendingIdleHandlers[i];
mPendingIdleHandlers[i] = null; // release the reference to the handler
boolean keep = false;
try {
// Execute the idleHandler task, and according to queueIdle return value
// Decide if this idleHandler should be used after the task is completed
// Remove idleHandles from the message queue
keep = idler.queueIdle();
} catch (Throwable t) {
Log.wtf(TAG, "IdleHandler threw exception", t);
}
// Remove if necessary
if(! keep) {synchronized (this) { mIdleHandlers.remove(idler); }}}// Reset the idleHandler count to 0 to ensure that it will not run again
pendingIdleHandlerCount = 0;
// By the time the call handles the idleHandler, a new Message may have already been sent
// or the pending time for other messages is up, at which point the message queue does not need to be suspended
nextPollTimeoutMillis = 0; }}Copy the code
IdleHandler Hook when the queue is idle
IdleHandler is a simple API that allows you to do things when the message queue is idle. IdleHandler itself is an interface:
private MessageQueue.IdleHandler myIdleHandler = new MessageQueue.IdleHandler() {
@Override
public boolean queueIdle(a) {
doSomething();
return false; }}; Looper.myQueue().addIdleHandler(myIdleHandler)Copy the code
Note that idleHandler is executed when Looper is idle. The timing of execution is not controlled, so you should know that you need this scenario. For example, if you need to initialize tasks, it is not appropriate to use idleHandler. If MessageQueue has messages to process, then there is no way to ensure that the corresponding task will be executed at the appropriate time.
Examples of scenarios where IdleHandler can be used are as follows:
- Activity startup optimization: Shorter but unnecessary code from onCreate, onStart, and onResume can be executed in IdleHandler, reducing startup time.
- In the past, when we wanted to do something after the View was drawn, we might have done it through the POST API, or we could have actually done it through IdleHandler.
Summary:
So far, the general process of message loop mechanism in Java layer has been finished, which involves several native calls. We will do analysis later. First, summarize how the whole message mechanism works in Java layer by means of flow chart.