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:

  1. Activity startup optimization: Shorter but unnecessary code from onCreate, onStart, and onResume can be executed in IdleHandler, reducing startup time.
  2. 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.