preface

As one of the most important parts of the system. Handler is a must-ask in an interview. This article just introduces a little IdleHandler from Handler. For additional details about Handler, refer to the interview questions I wrote about analyzing Handler from source code

What is IdleHandler

The following interface can be found in the Handler source code. The annotation section explains its function well. A simple overview

The idleHandler task is executed when there are no immediate messages in the message queue

public static interface IdleHandler {
    /** * Called when the message queue has run out of messages and will now * wait for more. Return true to keep your idle handler active, false * to have it removed. This may be called if there are still messages * pending in the queue, but they are all scheduled to be dispatched * after the current time. */
    boolean queueIdle();
}
Copy the code

How to use

Handler provides two methods

public void addIdleHandler(@NonNull IdleHandler handler) {
    if (handler == null) {
        throw new NullPointerException("Can't add a null IdleHandler");
    }
    synchronized (this) { mIdleHandlers.add(handler); }}Copy the code
public void removeIdleHandler(@NonNull IdleHandler handler) {
    synchronized (this) { mIdleHandlers.remove(handler); }}Copy the code

It is also very simple to use

Looper.myQueue().addIdleHandler{
    Log.i(TAG, "this is meeage")
    false
}
Copy the code

Source code analysis

Start with addIdleHandler. Add IdleHandler to mIdleHandlers

The mIdleHandlers are an array. Used to store idle tasks

private final ArrayList<IdleHandler> mIdleHandlers = new ArrayList<IdleHandler>();
Copy the code

We all know that the Handler pulls the message out of the MessageQueue. Let’s look at how the IdleHandler performs this task when there is no immediate message.

  Message next() {
 
        final long ptr = mPtr;
        if (ptr == 0) {
            return null;
        }
        // 👇 The default number of idle tasks is -1
        int pendingIdleHandlerCount = -1; // -1 only during first iteration
        int nextPollTimeoutMillis = 0;
        for (;;) {
            if(nextPollTimeoutMillis ! =0) {
                Binder.flushPendingCommands();
            }

            nativePollOnce(ptr, nextPollTimeoutMillis);

            synchronized (this) {
                // Try to retrieve the next message. Return if found.
                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 is null There are no messages in the queue. This is called the idle state
                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();
                        // 👇 Returns idle tasks when there are tasks
                        returnmsg; }}else {
                    // No more messages.
                    nextPollTimeoutMillis = -1;
                }

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

                // 👇 pendingIdleHandlerCount The default is -1
                if (pendingIdleHandlerCount < 0
                        // When there is no current message
                        && (mMessages == null || now < mMessages.when)) {
                    // Record the number of idle tasks we saved
                    pendingIdleHandlerCount = mIdleHandlers.size();
                }
                if (pendingIdleHandlerCount <= 0) {
                    // No idle handlers to run. Loop and wait some more.
                    mBlocked = true;
                    continue;
                }
                // 👇 default null creates an array for idle tasks
                if (mPendingIdleHandlers == null) {
                    mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
                }
                mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
            }

            
            // 👇 Execute idle tasks
            for (int i = 0; i < pendingIdleHandlerCount; i++) {
                final IdleHandler idler = mPendingIdleHandlers[i];
                mPendingIdleHandlers[i] = null; // release the reference to the handler

                boolean keep = false;
                try {
                    👇 idle interface has a return value.
                    keep = idler.queueIdle();
                } catch (Throwable t) {
                    Log.wtf(TAG, "IdleHandler threw exception", t);
                }
                
                👇 executes only once when the return value is false. The loop is executed when true is returned
                if(! keep) { synchronized (this) {
                        // 👇 If false is returned, the idle task is deletedmIdleHandlers.remove(idler); }}}// 👇 Resets the number of idle tasks
            // Reset the idle handler count to 0 so we do not run them again.
            pendingIdleHandlerCount = 0;

            // While calling an idle handler, a new message could have been delivered
            // so go back and look again for a pending message without waiting.
            nextPollTimeoutMillis = 0; }}Copy the code