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() 读取。
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 必须原样交给 后续两个结束方法之一,而且每条消息只能完成一次。
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 代码可以直接调用的接口。
/** @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 的开始和结束回调使用同一个快照。
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 回收。
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 重新抛出原异常。
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 对象。
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 计时起点。
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 区间。
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 线程同步执行,都不能观察未出队消息。
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 没有共享状态。
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。
public static void setObserver(@Nullable Observer observer) {
sObserver = observer;
}这也是 Observer 不能由多个组件独立注册的边界:后一次设置会替换静态字段。若系统需要多个 消费者,应由一个组合 Observer 在内部完成分发,而不是假设 Looper 保存观察者列表。
13. 动态替换
源码文件:frameworks/base/core/java/android/os/Looper.java
替换 Observer 的消息级效果如下:
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
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。
