Skip to content

Looper主循环

从 Looper.loop 到 MessageQueue.next,拆开阻塞、分发、异常、监控与消息回收的完整循环。

基于android-17.0.0_r1
AndroidLooperMessageQueueMessage源码阅读

Looper主循环 ​

本文承接Looper线程绑定。前文解决了“哪个线程拥有哪个 Looper”;本文解决“这个线程如何一直工作”。阅读主线是 Looper.loop() → loopOnce() → MessageQueue.next() → msg.target.dispatchMessage(msg),并沿着正常返回、队列退出、回调抛异常三条路径观察状态怎样收束。

本文不把 loop() 简化成一个无限 while。真正决定行为的是:next() 何时阻塞、何时返回 null,Looper 在分发前后保存哪些上下文,以及 Message 为什么只在正常路径进入 recycleUnchecked()。

1. 循环入口 ​

源码文件:frameworks/base/core/java/android/os/Looper.java

相关函数:loop()、loopOnce()

java
public static void loop() {
    final Looper me = myLooper();
    if (me == null) {
        throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
    }
    if (me.mInLoop) {
        Slog.w(TAG, "Loop again would have the queued messages be executed"
                + " before this one completed.");
    }
    me.mInLoop = true;
    Binder.clearCallingIdentity();
    final long ident = Binder.clearCallingIdentity();
    final int thresholdOverride = getThresholdOverride();
    for (;;) {
        if (!loopOnce(me, ident, thresholdOverride)) {
            return;
        }
    }
}

入口先查当前线程的 Looper,没有绑定就失败;已经在循环中再次调用只记录警告,不会创建第二个循环栈。ident 在循环开始时保存,后面每条消息完成后用于检查 Binder 身份是否被回调改变。

2. 取消息 ​

源码文件:frameworks/base/core/java/android/os/Looper.java

相关函数:loopOnce()

java
private static boolean loopOnce(final Looper me,
        final long ident, final int thresholdOverride) {
    Message msg = me.mQueue.next(); // might block
    if (msg == null) {
        // No message indicates that the message queue is quitting.
        return false;
    }
    // 后续执行日志、Observer、trace、分发和回收
}

loopOnce() 不自己判断消息时间,也不直接操作链表;它把“等待、到期、取出、退出”交给队列。只有拿到非 null 的 Message,Looper 才进入一次分发。因而 next() 返回 null 是退出协议,不是“暂时没有消息”。

3. 队列等待 ​

源码文件:frameworks/base/core/java/android/os/LegacyMessageQueue/MessageQueue.java

相关函数:next()

java
Message next() {
    final long ptr = mPtr;
    if (ptr == 0) {
        return null;
    }

    int nextPollTimeoutMillis = 0;
    for (;;) {
        nativePollOnce(ptr, nextPollTimeoutMillis);
        synchronized (this) {
            final long now = SystemClock.uptimeMillis();
            Message msg = mMessages;
            if (msg != null && msg.target == null) {
                do {
                    msg = msg.next;
                } while (msg != null && !msg.isAsynchronous());
            }
            if (msg != null) {
                if (now < msg.when) {
                    nextPollTimeoutMillis = (int) Math.min(
                            msg.when - now, Integer.MAX_VALUE);
                } else {
                    // 从链表摘下并返回到期消息
                    msg.markInUse();
                    return msg;
                }
            } else {
                nextPollTimeoutMillis = -1;
            }
            if (mQuitting) {
                dispose();
                return null;
            }
        }
    }
}

空队列使用 -1 继续等待;队头消息尚未到期时,把距离 when 的毫秒数交给 native poll;到期后在锁内摘下 Message。同步屏障存在时,队列会跳过屏障后面的同步消息,寻找异步消息,因此“链表头部”不一定就是本次返回的消息。

4. 分发准备 ​

源码文件:frameworks/base/core/java/android/os/Looper.java

相关函数:loopOnce()

java
final Printer logging = me.mLogging;
if (logging != null) {
    logging.println(">>>>> Dispatching to " + msg.target + " "
            + msg.callback + ": " + msg.what);
}
final Observer observer = sObserver;
final long traceTag = me.mTraceTag;
final long dispatchStart = needStartTime ? SystemClock.uptimeMillis() : 0;
Object token = null;
if (observer != null) {
    token = observer.messageDispatchStarting();
}
long origWorkSource = ThreadLocalWorkSource.setUid(msg.workSourceUid);

日志对象和 Observer 都在分发前复制到局部变量,避免回调期间配置变化影响这一条消息。dispatchStart 用于区分“消息送达慢”和“回调执行慢”;workSourceUid 则把消息携带的工作来源设置到当前线程,后续必须恢复。

5. 执行消息 ​

源码文件:frameworks/base/core/java/android/os/Looper.java

相关函数:loopOnce()、Handler.dispatchMessageImpl()

java
try {
    msg.target.dispatchMessage(msg);
    if (isLooperClearsThreadInterruptedEnabled()) {
        Thread.interrupted();
    }
    if (observer != null) {
        observer.messageDispatched(token, msg);
    }
    dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
} catch (Exception exception) {
    if (observer != null) {
        observer.dispatchingThrewException(token, msg, exception);
    }
    throw exception;
} finally {
    if (doctor != null) {
        doctor.stopMessageTimer(me.mAlarm);
    }
    ThreadLocalWorkSource.restore(origWorkSource);
    if (traceTag != 0) {
        Trace.traceEnd(traceTag);
    }
}

Looper 只负责把 Message 交给 target,具体 Runnable、Callback 和 handleMessage() 的选择由 Handler 完成。正常返回会通知 Observer;捕获到 Exception 时通知异常回调并重新抛出,不会被 Looper 静默吞掉。无论正常还是异常,finally 都负责停止计时、恢复 WorkSource 和结束 trace。

6. 完成清理 ​

源码文件:frameworks/base/core/java/android/os/Looper.java

相关函数:loopOnce()

java
if (logging != null) {
    logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
}

final long newIdent = Binder.clearCallingIdentity();
if (ident != newIdent) {
    Log.wtf(TAG, "Thread identity changed from 0x"
            + Long.toHexString(ident) + " to 0x"
            + Long.toHexString(newIdent));
}

msg.recycleUnchecked();
return true;

这些语句只位于 try/finally 成功返回之后:打印完成日志、检查 Binder identity、回收 Message,然后让外层循环取下一条。回调抛 Exception 时异常直接离开 loopOnce(),不会继续执行这里的完成日志和 recycleUnchecked();这也是为什么不能把“回调返回”笼统等同为“Message 已回收”。

7. 退出路径 ​

源码文件:frameworks/base/core/java/android/os/LegacyMessageQueue/MessageQueue.java

相关函数:quit(boolean)、next()

java
void quit(boolean safe) {
    if (!mQuitAllowed) {
        throw new IllegalStateException("Main thread not allowed to quit.");
    }
    synchronized (this) {
        if (mQuitting) {
            return;
        }
        mQuitting = true;
        if (safe) {
            removeAllFutureMessagesLocked();
        } else {
            removeAllMessagesLocked();
        }
        nativeWake(mPtr);
    }
}

退出线程设置 mQuitting 后唤醒 native poll。被唤醒的 next() 在锁内看到退出标志,释放 native 队列并返回 null;loopOnce() 再返回 false,外层 loop() 才结束。安全退出和立即退出的差异发生在队列清理阶段,Looper 本身没有另一套退出循环。

8. 异常路径 ​

异常路径有两个容易混淆的层次:Looper 会把 Exception 通知 Observer 后重新抛出;而 MessageQueue.next() 对 IdleHandler 使用单独的 Throwable 捕获,记录问题后继续等待。这意味着一个普通消息回调的异常不会变成“下一条消息继续处理”的保证,是否继续取消息取决于更外层的线程异常处理方式。

Message.recycleUnchecked() 的清理逻辑位于成功分发后的 Looper 代码,以及队列丢弃消息的路径;不能仅凭“发送成功”推断对象已经回收到池中。

9. 动手验证 ​

先定位三段主线:

bash
rg -n "loopOnce|mQueue\.next|msg\.target\.dispatchMessage|recycleUnchecked" \
  frameworks/base/core/java/android/os/Looper.java
rg -n "Message next\(|nativePollOnce|mQuitting|dispose\(\)" \
  frameworks/base/core/java/android/os/LegacyMessageQueue/MessageQueue.java

用 TestableLooper 可以把“入队”和“处理”拆开观察:先 Handler.post(),断言 Runnable 尚未执行,再调用 processAllMessages(),断言 Runnable 执行。这个输入验证的是队列处理边界,不是 native poll 的真实阻塞时长,也不覆盖异常后线程是否由宿主重启。

10. 边界说明 ​

本文只追踪 Android 17 LegacyMessageQueue 的 Java 主循环。同步屏障的完整插入/移除、JNI 到 native Looper 的 epoll 实现、Message 对象池容量和 ANR 判定分别属于后续主题。读者可以从 MessageQueue.next() 的 nativePollOnce 继续进入 JNI,但不能把本文的 Java 返回时序直接当作 native 调度策略。