Principle and application of IdleHandler

Preface

When reviewing the source code of Handler recently, I saw that there is a addIdleHandler(IdleHandler handler)method in MessageQueue whose parameters need to be passed IdleHandlerand saved in an interface mIdleHandlers.

public void addIdleHandler(@NonNull IdleHandler handler) {
    
    
        if (handler == null) {
    
    
            throw new NullPointerException("Can't add a null IdleHandler");
        }
        synchronized (this) {
    
    
            mIdleHandlers.add(handler);
        }
    }
   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();
    }

The function of this interface is to call back when there is no message that can be processed in the MessageQueue queueIdle. The interface has only one return value. If it returns false, it will be mIdleHandlersdeleted from the collection, otherwise it will be retained.

Source code

We all know that when Handler processes messages, it calls MessageQueue.next()methods through loops , so I just look at the source code of next:

Message next() {
    
    
        // Return here if the message loop has already quit and been disposed.
        // This can happen if the application tries to restart a looper after quit
        // which is not supported.
        final long ptr = mPtr;
        if (ptr == 0) {
    
    
            return null;
        }

        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 != 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();
                        return msg;
                    }
                } else {
    
    
                    // No more messages.
                    nextPollTimeoutMillis = -1;
                }

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

                // If first time idle, then get the number of idlers to run.
                // Idle handles only run if the queue is empty or if the first message
                // in the queue (possibly a barrier) is due to be handled in the future.
              	// 这里才是处理IdelHandler消息的位置
              	// 第一次进入,消息队列为空或者当前的时间小于将要处理消息的目标时间
                if (pendingIdleHandlerCount < 0
                        && (mMessages == null || now < mMessages.when)) {
    
    
                    pendingIdleHandlerCount = mIdleHandlers.size();
                }
              	//mIdleHandlers中为空,阻塞
                if (pendingIdleHandlerCount <= 0) {
    
    
                    // No idle handlers to run.  Loop and wait some more.
                    mBlocked = true;
                    continue;
                }
              	//下面是将mIdleHandlers集合转化数组
                if (mPendingIdleHandlers == null) {
    
    
                    mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
                }
                mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
            }

            // Run the idle handlers.
            // We only ever reach this code block during the first iteration.
          	// 遍历数组回调queueIdle()
            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 {
    
    
                    keep = idler.queueIdle();
                } catch (Throwable t) {
    
    
                    Log.wtf(TAG, "IdleHandler threw exception", t);
                }

              	//判断取出的返回值为false就idler从mIdleHandlers删除
                if (!keep) {
    
    
                    synchronized (this) {
    
    
                        mIdleHandlers.remove(idler);
                    }
                }
            }

            // 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;
        }
    }

scenes to be used

In the past, when we got the width and height of a certain control in Activity, we always got 0 because the view measurement has not been completed. The usual method is to monitor ViewTreeObserver, which is to ViewRootImplcall the ViewTreeObserver.dispatchOnGlobalLayout()method after the measurement is completed . At this time onGlobalLayout, the width and height of the control obtained in the callback are all correct data.

Now we can use the IdleHandlerimplementation to process the messages in the IdleHandler after all UI message processing is completed, so that the width and height of the control can be obtained correctly. The code is as follows:

override fun onCreate(savedInstanceState: Bundle?) {
    
    
        super.onCreate(savedInstanceState)
        Looper.myQueue().addIdleHandler {
    
    
            Loger.e("--Idle--${
      
      mView.width}${
      
      mView.height}----")
            false
        }
    }

Guess you like

Origin blog.csdn.net/ZYJWR/article/details/103086664