源码实现

MessageQueue中next()方法关于IdleHandler的逻辑:

源码

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.
                if (pendingIdleHandlerCount < 0
                        && (mMessages == null || now < mMessages.when)) {
                    pendingIdleHandlerCount = mIdleHandlers.size();
                }
                if (pendingIdleHandlerCount <= 0) {
                    // No idle handlers to run.  Loop and wait some more.
                    mBlocked = true;
                    continue;
                }

                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.
            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);
                }

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

整体作用回顾:MessageQueue.next()

该方法被 Looper.loop() 持续调用,用于:

  • 从 MessageQueue 中取出下一个需要处理的 Message;
  • 如果暂时没有消息可处理,就会等待,并可能调用 IdleHandler;
  • 如果调用了 Looper.quit() 或 quitSafely(),就会返回 null 终止循环。

IdleHandler 的触发时机

if (pendingIdleHandlerCount < 0
        && (mMessages == null || now < mMessages.when)) {
    pendingIdleHandlerCount = mIdleHandlers.size();
}

满足两个条件时会触发 IdleHandler:

  1. 当前没有消息(mMessages == null),即队列空了;
  2. 或者有消息,但 该消息的执行时间还没到(now < mMessages.when)。

这说明:只在队列“暂时空闲”时,IdleHandler 才会被调度执行。

IdleHandler 的执行逻辑

for (int i = 0; i < pendingIdleHandlerCount; i++) {
    final IdleHandler idler = mPendingIdleHandlers[i];
    ...
    boolean keep = idler.queueIdle();
    ...
    if (!keep) {
        synchronized (this) {
            mIdleHandlers.remove(idler);
        }
    }
}

每个 IdleHandler 会被执行:

  • 如果 queueIdle() 返回 true:会保留在 mIdleHandlers 中,下次继续执行;
  • 返回 false:移除,不再执行。

执行完所有 idle handlers 后,重置 pendingIdleHandlerCount = 0,然后继续 loop。

完整 IdleHandler 生命周期流程

  1. 调用 Looper.myQueue().addIdleHandler(…) 注册;
  2. 当队列空闲(无消息或下一条未到时);
  3. queueIdle() 被调用;
  4. 若返回 false,会从 mIdleHandlers 中移除;
  5. 如果队列重新被填充,IdleHandler 不会触发;
  6. 下一次空闲,仍存在的 IdleHandler 会再被执行。

为啥只在首次遍历时调用?

// We only ever reach this code block during the first iteration.

因为:

  • IdleHandler 被认为是“空闲时的补充执行”,一旦有消息入队,就优先处理消息。
  • 如果你在 queueIdle() 中再次入队消息,那么下一轮 next() 时将不会再触发 IdleHandler,逻辑会重新回到主循环,优先处理消息。

IdleHandler 是线程安全的吗?

基本安全,但:

  • 添加 (addIdleHandler) 和删除 (removeIdleHandler) 发生在 MessageQueue 的同步块中;
  • 所以推荐只在当前 Looper 所在线程操作,避免并发添加/移除导致不一致。

总结 IdleHandler 的执行逻辑

步骤 条件
1. 是否进入 IdleHandler mMessages == null 或 now < mMessages.when
2. 是否调用 queueIdle() 是(每个已注册 IdleHandler 都调用)
3. queueIdle() 返回值 true → 保留,false → 删除
4. 执行顺序 所有注册的 IdleHandler
5. 中断条件 有消息入队、或者 quit()

真实使用建议

  • 尽量轻量:不要在 queueIdle() 中执行复杂耗时任务,否则会阻塞主线程。
  • 不要依赖它一定执行,消息队列活跃时可能完全不触发。
  • 适合用于延迟初始化、预加载等“有最好,没有也行”的任务。

当前网速较慢或者你使用的浏览器不支持博客特定功能,请尝试刷新或换用Chrome、Firefox等现代浏览器