Skip to content

Looper Observer 机制

从 Looper.loopOnce 追踪 Observer 的 token 配对、正常/异常完成、全局安装和与 Printer/Trace 的边界。

基于android-17.0.0_r1
AndroidLooperObserver性能调试源码阅读

Looper Observer 机制 ​

Looper.Observer 是 Android 17 中用于消息派发观测的隐藏接口。它和 setMessageLogging(Printer) 都位于 Looper.loopOnce(),但 Observer 直接拿到 Message 和 一个自定义 token,并在异常路径收到专门回调;Printer 只能看到字符串,而且正常结束日志 不覆盖所有异常分支。

本文面向已经读过 Looper消息监控、Handler消息处理 和 Looper主循环 的读者。本文只回答当前源码的 四个问题:Observer 在 dispatch 前后何时调用,token 如何配对,Observer 是按 Looper 还是按 进程安装,以及它和 Printer、traceTag、slow log 的边界是什么。不把 hidden API 写成普通应用 公开接口,也不复制归档中未经源码支持的系统监控实现。

读完后,读者应能写出一个 token 记录器,解释正常和异常 dispatch 为什么只能回调其中一个 结束方法,判断 Observer 是否能观察到尚未出队的消息,并知道动态替换全局 Observer 时应考虑 哪些消息级边界。

1. 全局字段 ​

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

Looper 同时保存实例级的 Printer/trace/slow 阈值和进程级静态 sObserver。因此 Printer 可以 按 Looper 设置,而 Observer 一次设置会被所有 Looper 的 loopOnce() 读取。

java
private static Observer sObserver;
private Printer mLogging;
private long mTraceTag;
private long mSlowDispatchThresholdMs;
private long mSlowDeliveryThresholdMs;

Observer 的全局性不意味着它在独立线程运行;它的三个回调仍然在正在派发 Message 的 Looper 线程同步执行。一个慢 Observer 会直接增加所有受影响消息的 dispatch 时间。

2. 接口契约 ​

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

Observer 暴露三个方法:开始、正常完成、异常完成。开始方法返回 token,token 必须原样交给 后续两个结束方法之一,而且每条消息只能完成一次。

java
public interface Observer {
    Object messageDispatchStarting();

    void messageDispatched(Object token, Message msg);

    void dispatchingThrewException(Object token, Message msg, Exception exception);
}

token 类型由实现者决定;源码不要求它是时间戳、对象或整数。真正的不变量是配对关系:

入口成功路径异常路径
messageDispatchStarting()messageDispatched(token, msg)dispatchingThrewException(token, msg, ex)

Observer 不接收 MessageQueue 中尚未出队的消息,也不接收已经被 removeMessages() 或 quit 清理掉的消息。

3. 安装入口 ​

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

setObserver() 只写静态字段,没有创建包装对象或线程;传入 null 表示关闭。它是 hidden API, 所以文章只用于理解 framework 内部观测契约,不把它当成普通 SDK 代码可以直接调用的接口。

java
/** @hide */
public static void setObserver(@Nullable Observer observer) {
    sObserver = observer;
}

安装时机影响覆盖范围:安装前已经完成的 Message 不会补发事件;替换 Observer 后,正在执行的 Message 已经在 loopOnce() 中保存了旧 Observer 快照。

4. 快照读取 ​

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

loopOnce() 在 Printer 日志之后读取 sObserver 到局部变量,注释明确说明这样做是为了避免 dispatch 期间 Observer 被修改。当前 Message 的开始和结束回调使用同一个快照。

java
final Printer logging = me.mLogging;
if (logging != null) {
    logging.println(">>>>> Dispatching to " + msg.target + " "
            + msg.callback + ": " + msg.what);
}

// Make sure the observer won't change while processing a transaction.
final Observer observer = sObserver;

Object token = null;
if (observer != null) {
    token = observer.messageDispatchStarting();
}

如果另一个线程在此之后调用 setObserver(newObserver),当前 Message 仍由旧 Observer 完成; 新 Observer 从下一条进入 loopOnce() 的消息开始生效。这是字段快照的时序,不是 volatile 可见性承诺。

5. 正常配对 ​

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

Handler dispatch 正常返回后,Looper 把 token 和 Message 交给 messageDispatched(),随后 才记录 dispatch end、slow log、Printer 结束文本和 Message 回收。

java
try {
    msg.target.dispatchMessage(msg);
    if (observer != null) {
        observer.messageDispatched(token, msg);
    }
    dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
} catch (Exception exception) {
    // 异常分支
} finally {
    ThreadLocalWorkSource.restore(origWorkSource);
    if (traceTag != 0) {
        Trace.traceEnd(traceTag);
    }
}

messageDispatched() 执行时 Message 仍未 recycleUnchecked(),因此 Observer 可以读取 what、target、callback 等字段;但 Observer 不应保存 Message 引用,因为随后字段会被清理 并可能进入对象池复用。

6. 异常配对 ​

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

如果 dispatchMessage() 抛出 Exception,正常完成方法不会被调用;Observer 只收到 dispatchingThrewException(),然后 Looper 重新抛出原异常。

java
try {
    msg.target.dispatchMessage(msg);
    if (observer != null) {
        observer.messageDispatched(token, msg);
    }
} catch (Exception exception) {
    if (observer != null) {
        observer.dispatchingThrewException(token, msg, exception);
    }
    throw exception;
}

这里的异常类型是 Exception,不是任意 Throwable;更高层线程异常策略仍决定进程或 Looper 是否继续。Observer 的异常回调完成后,原异常不会被吞掉,也不会自动转成任务失败结果。

7. 清理顺序 ​

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

无论正常还是异常,finally 都恢复 WorkSource 和结束 trace;但 Message recycle 和 Printer 结束文本处于后续路径。Observer 如果要复制诊断信息,应在自己的回调中提取最小字段,而不是 异步保存 Message 对象。

text
messageDispatchStarting -> dispatchMessage
  正常: messageDispatched -> slow log -> Printer finished -> Message.recycle
  异常: dispatchingThrewException -> throw -> finally 清理

异常路径不保证 Printer <<<<< Finished,也不保证后续 slow dispatch 统计;Observer 的异常 回调是可靠配对点,但它也不能阻止异常继续传播。

8. 时间 token ​

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

一个最小 token 可以保存 dispatch 开始时间和线程身份,再在正常/异常结束时计算区间。区间 起点应放在 messageDispatchStarting(),而不是 Message.when;后者是 delivery 计时起点。

java
final class DispatchToken {
    final long startUptime = SystemClock.uptimeMillis();
    final long threadId = Thread.currentThread().getId();
}

final Looper.Observer observer = new Looper.Observer() {
    @Override
    public Object messageDispatchStarting() {
        return new DispatchToken();
    }

    @Override
    public void messageDispatched(Object token, Message msg) {
        DispatchToken t = (DispatchToken) token;
        long elapsed = SystemClock.uptimeMillis() - t.startUptime;
        // 读取 msg.what/target 后立即丢弃 token
    }

    @Override
    public void dispatchingThrewException(Object token, Message msg, Exception ex) {
        // 记录 token、msg.what 和异常后立即丢弃 token
    }
};

这段示例说明 token 责任,不代表公开应用可以直接安装 Observer。Observer 回调同步运行, token 创建和记录动作本身也会计入消息 dispatch 时间。

9. Slow log 对照 ​

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

Looper 的 slow log 不是 Observer 的内部实现,而是 loopOnce() 读取阈值后独立计算。delivery 使用 msg.when → dispatchStart,dispatch 使用 dispatchStart → dispatchEnd;Observer token 如果只保存 dispatchStart,只能复现 dispatch 区间。

java
final boolean logSlowDelivery = (slowDeliveryThresholdMs > 0 || hasOverride)
        && (msg.when > 0);
final boolean logSlowDispatch = (slowDispatchThresholdMs > 0 || hasOverride);

if (logSlowDelivery) {
    showSlowLog(slowDeliveryThresholdMs, msg.when, dispatchStart, "delivery", msg);
}
if (logSlowDispatch) {
    showSlowLog(slowDispatchThresholdMs, dispatchStart, dispatchEnd, "dispatch", msg);
}

Observer 可以自己记录两个时间点,但必须理解 Message.when 的语义和 front-of-queue/特殊消息 没有统一 delivery 起点的边界。

10. Printer 对照 ​

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

Printer 是 Looper 实例字段,Observer 是进程级静态字段;Printer 接收字符串,Observer 接收 Message 和异常对象。二者都在 dispatch 线程同步执行,都不能观察未出队消息。

java
final Printer logging = me.mLogging;
if (logging != null) {
    logging.println(">>>>> Dispatching to " + msg.target + " "
            + msg.callback + ": " + msg.what);
}

final Observer observer = sObserver;
if (observer != null) {
    token = observer.messageDispatchStarting();
}

如果一个进程同时设置二者,调用顺序是 Printer 开始文本先于 Observer start;结束顺序则先 Observer/slow log,再 Printer finished。监控工具不应假设二者只有一个会存在。

11. Trace 边界 ​

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

Looper 的 mTraceTag 负责在 dispatch 期间开启 trace section;它直接使用 Handler 的 getTraceName(msg),与 Observer token 没有共享状态。

java
final long traceTag = me.mTraceTag;
if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
    Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
}

try {
    msg.target.dispatchMessage(msg);
} finally {
    if (traceTag != 0) {
        Trace.traceEnd(traceTag);
    }
}

Trace 展示的是时间轴 section,Observer 是同步代码钩子;开启 trace 不会替代异常回调,安装 Observer 也不会自动产生 Perfetto slice。

12. 全进程影响 ​

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

静态 sObserver 被每个 Looper 的 loopOnce() 读取,因此安装一个 Observer 会影响主线程、 HandlerThread 和其它已经进入 Looper.loop 的线程。Observer 自身不能从参数知道“这是哪个 Looper”,需要 token 中保存线程信息,或从当前线程读取名称/id。

java
public static void setObserver(@Nullable Observer observer) {
    sObserver = observer;
}

这也是 Observer 不能由多个组件独立注册的边界:后一次设置会替换静态字段。若系统需要多个 消费者,应由一个组合 Observer 在内部完成分发,而不是假设 Looper 保存观察者列表。

13. 动态替换 ​

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

替换 Observer 的消息级效果如下:

text
Message A: 读取 oldObserver -> start(old) -> dispatch -> finish(old)
setObserver(newObserver)
Message B: 读取 newObserver -> start(new) -> dispatch -> finish(new)

如果替换发生在 A 的 dispatch 期间,A 的 token 仍交回 oldObserver;newObserver 不会收到 A 的 结束通知。安装/替换逻辑若需要完整窗口,应在外层同步,不能在 Observer 回调中假设静态字段 始终指向自己。

14. 验证输入 ​

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

第一组验证正常配对:在受控 HandlerThread 上设置 Observer,投递普通 Runnable,断言事件顺序 是 start → dispatch → messageDispatched,token 对象与 Message 只在结束回调中配对一次。

第二组验证异常配对:投递抛出 RuntimeException 的 Runnable,断言调用 dispatchingThrewException(),而不是 messageDispatched();同时接受 Looper 可能结束或 线程异常处理接管。

第三组验证全局范围:创建两个 Looper 线程,安装一个 Observer,分别投递消息并记录 token 中的线程 id;断言两个 Looper 都经过同一 Observer 实例。再替换 Observer,验证已开始消息 仍由旧 Observer 完成。

这些输入证明源码中的调用顺序和快照边界,不证明 Observer 可用于普通 SDK 应用,也不证明 回调监控没有性能成本。

15. 复查命令 ​

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

bash
rg -n "interface Observer|setObserver|sObserver" \
  frameworks/base/core/java/android/os/Looper.java

rg -n "messageDispatchStarting|messageDispatched|dispatchingThrewException" \
  frameworks/base/core/java/android/os/Looper.java

rg -n "final Observer observer|msg\.target\.dispatchMessage|msg\.recycleUnchecked" \
  frameworks/base/core/java/android/os/Looper.java

rg -n "setMessageLogging|setSlowLogThresholdMs|mTraceTag|traceBegin" \
  frameworks/base/core/java/android/os/Looper.java

复查一条 Observer 记录时,按“局部快照 → start token → dispatch → 正常/异常唯一结束 → trace/ slow log → recycle”走源码。不要用 Printer 的结束字符串推断 Observer 已完成,也不要把 Message 保存到长期 telemetry 对象中。

16. 适用边界 ​

Observer 适合 framework 内部对多个 Looper 做结构化 dispatch 观测,尤其适合异常配对和不依赖 字符串解析的 telemetry。它不提供队列积压扫描、任务取消、异常吞吐或根因定位,也不改变 Message 的线程和生命周期。

当需要监控某条消息时,先决定要测 delivery、dispatch 还是异常,再选择 slow log、Printer 或 Observer;如果只是普通应用代码,应遵守公开 API 边界并使用可用的日志/trace/诊断机制,而 不是把 hidden Observer 当作稳定 SDK。