ViewRootHandler 绘制路径
ViewRootImpl.ViewRootHandler 不是“所有 View 操作的总开关”,也不是一个独立渲染线程。它是 ViewRootImpl 持有的普通 Handler:消息在创建 ViewRootImpl 的 Looper 上执行,绘制请求由 scheduleTraversals() 去重后插入同步屏障,再交给 Choreographer 的 VSYNC 回调触发。
本文面向已经读过 ActivityThread.H 主线程、 异步消息与 VSYNC、同步屏障 和 Handler消息发送 的读者。本文选取三条源码 路径:requestLayout()/invalidate() 如何汇聚到 traversal,窗口和输入回调如何进入 ViewRootHandler,以及消息、屏障、VSYNC 和 performTraversals() 的生效顺序。它不展开 performTraversals() 内部全部 measure/layout/draw 算法,也不把每个 MSG_* 常量当成独立专题。
读完后,读者应能定位 ViewRootHandler 的 owner 和 Looper,解释为什么多个 requestLayout() 不会在同一帧重复注册 traversal,判断普通消息为什么可能被屏障延后,以及从 WMS resize 或 InputEventReceiver 入口找到主线程消费和失败清理路径。
1. 对象归属
源码文件:frameworks/base/core/java/android/view/ViewRootImpl.java
ViewRootImpl 构造时把 mHandler 传给 View.AttachInfo,同时获取当前线程的 Choreographer。因此 ViewRootHandler、ViewRootImpl 状态、View 树和 Choreographer 都围绕 创建 ViewRootImpl 的 Looper 组织,而不是由 Handler 自己创建线程。
mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this,
context);
mChoreographer = Choreographer.getInstance();ViewRootImpl 还把同一个 mHandler 用于显示监听、无障碍监听等回调,并把它包装成 HandlerExecutor 供需要 Executor 的 API 使用;这说明 Handler 的 owner 是 ViewRootImpl, 消费者可以是 View 系统或其他 framework 回调。
DisplayManagerGlobal
.getInstance()
.registerDisplayListener(
mDisplayListener,
mHandler,
eventsToBeRegistered,
mBasePackageName);| 对象 | 所有者 | 主要消费者 | 线程来源 |
|---|---|---|---|
ViewRootImpl | 窗口视图根 | traversal、窗口状态、输入队列 | 创建 ViewRootImpl 的线程 |
mHandler | ViewRootImpl | ViewRootHandler 消息分支 | mLooper |
mChoreographer | 当前 Looper | VSYNC callback | 同一 Looper |
mView | ViewRootImpl | measure/layout/draw、输入分发 | UI 线程 |
2. 消息入口
源码文件:frameworks/base/core/java/android/view/ViewRootImpl.java
ViewRootHandler 继承普通 Handler,使用 getMessageName() 给消息提供可读名称,并在 handleMessage() 外层统一进行 View trace。真正的 case 分支位于 handleMessageImpl(),这样 Trace 的结束可以由 finally 保证。
final class ViewRootHandler extends Handler {
@Override
public String getMessageName(Message message) {
switch (message.what) {
case MSG_INVALIDATE:
return "MSG_INVALIDATE";
case MSG_RESIZED:
return "MSG_RESIZED";
case MSG_DISPATCH_INPUT_EVENT:
return "MSG_DISPATCH_INPUT_EVENT";
}
return super.getMessageName(message);
}
@Override
public void handleMessage(Message msg) {
if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, getMessageName(msg));
}
try {
handleMessageImpl(msg);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
}这里的 finally 只保证 trace 配对,不会捕获并吞掉 handleMessageImpl() 的业务异常。消息 仍然在 Looper 的普通 Handler 分发边界中执行,异常传播和线程退出要继续回到 Looper 源码。
3. 布局请求
源码文件:frameworks/base/core/java/android/view/ViewRootImpl.java
requestLayout() 的最终目标是 scheduleTraversals()。该方法先检查线程,再用 mTraversalScheduled 做去重;只有从“未安排”变为“已安排”时,才插入屏障并注册 VSYNC 回调。
void scheduleTraversals() {
checkThreadCompat();
if (!mTraversalScheduled) {
mTraversalScheduled = true;
postTraversalBarrier();
mChoreographer.postVsyncCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalCallback);
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}mTraversalScheduled 是 ViewRootImpl 的状态,不是 MessageQueue 的去重。两个 View 在同一帧 内分别调用 requestLayout(),都可能沿父链到达 ViewRootImpl,但第二次只看到标志已设置, 不会再插入第二个 traversal barrier 或注册第二个 callback。
3.1 线程边界
源码文件:frameworks/base/core/java/android/view/ViewRootImpl.java
ViewRootImpl 通过 checkThreadCompat() 约束需要 UI 线程的入口;它不是把任意线程调用自动 转发到 mHandler。错误线程是否抛出或记录,受当前兼容变更和检查配置影响。
void checkThread() {
Thread current = Thread.currentThread();
if (mThread != current) {
throwCalledFromWrongThreadException();
}
}
private void throwCalledFromWrongThreadException() {
throw newCalledFromWrongThreadException(/*withInitStack=*/true);
}这和 postInvalidate() 是两条不同的 API 契约:需要修改 ViewRootImpl 状态的布局请求要求 原始线程;跨线程的无效区域请求则显式封装成 Handler 消息。
4. 屏障注册
源码文件:frameworks/base/core/java/android/view/ViewRootImpl.java
postTraversalBarrier() 把同步屏障 token 保存在 ViewRootImpl。当前实现还支持原子屏障字段, 在重复设置时负责移除被替换的旧 token,避免 barrier 泄漏。
private void postTraversalBarrier() {
int newBarrier = mQueue.postSyncBarrier();
if (mAtomicTraversalBarrier) {
final long oldBarrier = mTraversalBarrierAtomic.getAndSet(newBarrier);
if (oldBarrier != UNSET_TRAVERSAL_BARRIER) {
mQueue.removeSyncBarrier((int) oldBarrier);
}
} else {
mTraversalBarrier = newBarrier;
}
}
private void removeTraversalBarrier() {
if (mAtomicTraversalBarrier) {
final long oldBarrier = mTraversalBarrierAtomic.getAndSet(UNSET_TRAVERSAL_BARRIER);
if (oldBarrier != UNSET_TRAVERSAL_BARRIER) {
mQueue.removeSyncBarrier((int) oldBarrier);
}
} else {
mQueue.removeSyncBarrier(mTraversalBarrier);
}
}屏障的 owner 是 ViewRootImpl,MessageQueue 只保存屏障节点。屏障的生效时机从注册开始, 直到 doTraversal() 移除;它不是永久优先级,也不是把所有消息停止,而是让同步消息在 屏障之后等待异步消息或屏障移除。
5. VSYNC 注册
源码文件:frameworks/base/core/java/android/view/ViewRootImpl.java
屏障注册后,ViewRootImpl 把 traversal callback 交给 Choreographer。此处的 callback 不是 mHandler.postDelayed(),而是 Choreographer 管理的 VSYNC 调度;后续由 callback 进入 doTraversal(frameTimeNanos)。
mChoreographer.postVsyncCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalCallback);这条路径把三个状态分开:
| 状态 | owner | 生效时机 |
|---|---|---|
mTraversalScheduled | ViewRootImpl | 第一次 schedule 时写入 |
| traversal barrier | MessageQueue/ViewRootImpl token | 注册后阻挡同步消息 |
| VSYNC callback | Choreographer | 下一个显示帧调度阶段 |
所以“调用 requestLayout() 后立刻 measure”不符合源码顺序。调用只把一次 traversal 标记为 待执行;真正的测量和布局要等待 Choreographer 的 frame 回调。
这张图表达的是 traversal 的 owner 关系,不表示 Choreographer 直接调用 ViewRootHandler。 在 Android 17 的实现中,ViewRootImpl 的 traversal callback 由 Choreographer 帧阶段调用, ViewRootHandler 仍负责其他窗口和输入消息。
6. 遍历执行
源码文件:frameworks/base/core/java/android/view/ViewRootImpl.java
doTraversal() 只在状态仍为已安排时继续:先清除标志,移除屏障,再进入 performTraversals(frameTimeNanos)。这保证一次 callback 消费掉当前预约,并释放同步消息 继续处理的条件。
void doTraversal(long frameTimeNanos) {
if (mTraversalScheduled) {
mTraversalScheduled = false;
removeTraversalBarrier();
performTraversals(frameTimeNanos);
}
}这里的顺序很重要:屏障在 performTraversals() 之前移除,而不是绘制结束后才移除。否则 traversal 期间可能阻塞需要在绘制逻辑中重新入队的同步消息。performTraversals() 具体如何 测量窗口、调用 View 树和提交绘制,属于 View 系统的更深专题;本文只把它作为 traversal 消费者定位。
7. 无效区域
源码文件:frameworks/base/core/java/android/view/ViewRootImpl.java
跨线程的 postInvalidate 路径不直接访问 ViewRootImpl 的布局状态,而是把 View 或 InvalidateInfo 放入 Handler 消息。延迟参数最终由 Handler 的 sendMessageDelayed() 转换 成 Message.when。
public void dispatchInvalidateDelayed(View view, long delayMilliseconds) {
Message msg = mHandler.obtainMessage(MSG_INVALIDATE, view);
mHandler.sendMessageDelayed(msg, delayMilliseconds);
}
public void dispatchInvalidateRectDelayed(AttachInfo.InvalidateInfo info,
long delayMilliseconds) {
final Message msg = mHandler.obtainMessage(MSG_INVALIDATE_RECT, info);
mHandler.sendMessageDelayed(msg, delayMilliseconds);
}消息到达后,ViewRootHandler 调用目标 View 的 invalidate(),或者处理区域信息并回收 InvalidateInfo。消息本身不直接调用 performTraversals();无效化会通过 View 父链再次 汇聚到 traversal 调度。
case MSG_INVALIDATE:
((View) msg.obj).invalidate();
break;
case MSG_INVALIDATE_RECT:
final View.AttachInfo.InvalidateInfo info =
(View.AttachInfo.InvalidateInfo) msg.obj;
info.target.invalidate(info.left, info.top, info.right, info.bottom);
info.recycle();
break;7.1 取消路径
源码文件:frameworks/base/core/java/android/view/ViewRootImpl.java
cancelInvalidate() 按消息类型、Handler 和对象匹配移除尚未取出的消息,同时清理 mInvalidateOnAnimationRunnable 中的记录。它不能撤回已经从 MessageQueue 摘出的消息。
public void cancelInvalidate(View view) {
mHandler.removeMessages(MSG_INVALIDATE, view);
mHandler.removeMessages(MSG_INVALIDATE_RECT, view);
mInvalidateOnAnimationRunnable.removeView(view);
}因此取消成功只表示队列中匹配项被移除;如果消息已经进入 handleMessageImpl(),业务代码 可能已经调用了 View.invalidate。需要更强取消语义时,必须在调用方维护自己的状态标志。
8. 输入消息
源码文件:frameworks/base/core/java/android/view/ViewRootImpl.java
输入事件消息在投递点显式标记为异步,保证 traversal barrier 存在时仍能被 MessageQueue 寻找。它仍然在 ViewRootHandler 所属 Looper 上串行执行,不会由异步位创建输入线程。
public void dispatchInputEvent(InputEvent event, InputEventReceiver receiver) {
SomeArgs args = SomeArgs.obtain();
args.arg1 = event;
args.arg2 = receiver;
Message msg = mHandler.obtainMessage(MSG_DISPATCH_INPUT_EVENT, args);
msg.setAsynchronous(true);
mHandler.sendMessage(msg);
}消息消费先把事件加入输入队列,再回收 SomeArgs;后续输入处理由 doProcessInputEvents()/输入队列继续负责。
case MSG_DISPATCH_INPUT_EVENT: {
SomeArgs args = (SomeArgs) msg.obj;
InputEvent event = (InputEvent) args.arg1;
InputEventReceiver receiver = (InputEventReceiver) args.arg2;
enqueueInputEvent(event, receiver, 0, true);
args.recycle();
break;
}异步消息只是越过屏障的选择资格;如果主线程正在执行一个长时间的 performTraversals() 或 其他 callback,输入仍需等待当前分发结束。这是“绕过屏障”和“抢占主线程”的区别。
9. 窗口回调
源码文件:frameworks/base/core/java/android/view/ViewRootImpl.java
窗口系统通过内部 W 接收 resize 回调。W 持有 ViewRootImpl 的 WeakReference,先判断 对象是否仍存活,再决定直接处理还是投递 MSG_RESIZED。当回调来自本地 transaction item 且线程就是 UI 线程时,可以直接调用 handleResized();其他情况进入 Handler。
static class W extends WindowClientTransactionHandler {
private final WeakReference<ViewRootImpl> mViewAncestor;
@Override
public void resized(WindowRelayoutResult layout, boolean reportDraw, boolean forceLayout,
int displayId, boolean syncWithBuffers, boolean dragResizing) {
final boolean isFromResizeItem = mIsFromTransactionItem;
mIsFromTransactionItem = false;
final ViewRootImpl viewAncestor = mViewAncestor.get();
if (viewAncestor == null) {
return;
}
if (isFromResizeItem && viewAncestor.mLooper
== ActivityThread.currentActivityThread().getLooper()) {
viewAncestor.handleResized(layout.frames, reportDraw, layout.mergedConfiguration,
layout.insetsState, forceLayout, displayId, layout.syncSeqId,
syncWithBuffers, dragResizing, layout.activityWindowInfo);
return;
}
viewAncestor.dispatchResized(layout, reportDraw, forceLayout, displayId,
syncWithBuffers, dragResizing);
}
}直接路径的条件比“Binder 回调总要 Handler 转发”更窄。对象已被回收、线程不匹配或来源不是 transaction item 时,代码会走消息路径或直接返回。
10. 尺寸消息
源码文件:frameworks/base/core/java/android/view/ViewRootImpl.java
dispatchResized() 把窗口 relayout 结果和控制参数打包进 SomeArgs,根据 reportDraw 选择 MSG_RESIZED_REPORT 或 MSG_RESIZED,再发送给 mHandler。
private void dispatchResized(WindowRelayoutResult layout, boolean reportDraw,
boolean forceLayout, int displayId, boolean syncWithBuffers, boolean dragResizing) {
Message msg = mHandler.obtainMessage((reportDraw && !NoPreloadHolder.sAlwaysSeqId)
? MSG_RESIZED_REPORT : MSG_RESIZED);
SomeArgs args = SomeArgs.obtain();
args.arg1 = layout;
args.argi1 = forceLayout ? 1 : 0;
args.argi2 = displayId;
args.argi3 = syncWithBuffers ? 1 : 0;
args.argi4 = dragResizing ? 1 : 0;
msg.obj = args;
mHandler.sendMessage(msg);
}消费时先判断 mAdded,再调用 handleResized(),最后回收 SomeArgs。窗口已经 detach 时, 消息仍可能到达,但处理函数会通过状态条件阻止继续使用已失效的窗口状态。
case MSG_RESIZED:
case MSG_RESIZED_REPORT: {
final SomeArgs args = (SomeArgs) msg.obj;
final WindowRelayoutResult layout = (WindowRelayoutResult) args.arg1;
final boolean reportDraw = msg.what == MSG_RESIZED_REPORT;
handleResized(layout.frames, reportDraw, layout.mergedConfiguration,
layout.insetsState, args.argi1 != 0, args.argi2, layout.syncSeqId,
args.argi3 != 0, args.argi4 != 0, layout.activityWindowInfo);
args.recycle();
break;
}11. 清理消息
源码文件:frameworks/base/core/java/android/view/ViewRootImpl.java
ViewRootHandler 还处理 MSG_DIE、insets、focus 和窗口移动等消息。以 MSG_DIE 为例, 消息消费者是 doDie(),而不是直接释放 Handler;ViewRootImpl 的窗口、监听器、Renderer 和 ViewRoot 状态由更大的 detach/cleanup 路径负责。
case MSG_DIE: {
doDie();
break;
}如果设置窗口失败,setView() 的异常路径会把 View 引用和 root 关联清空,调用 unscheduleTraversals(),再抛出窗口异常。这个清理顺序说明 traversal barrier 不是“最终一定 会由 VSYNC callback 移除”;窗口创建失败时必须主动取消。
} catch (RemoteException | RuntimeException e) {
mView = null;
mAttachInfo.mRootView = null;
mFallbackEventHandler.setView(null);
unscheduleTraversals();
setAccessibilityFocus(null, null);
throw new RuntimeException("Adding window failed", e);
}12. 线程检查
源码文件:frameworks/base/core/java/android/view/ViewRootImpl.java
ViewRootImpl 保存创建线程 mThread;checkThread() 发现不一致时构造 CalledFromWrongThreadException。这解释了为什么普通 View 修改不是自动发送到 ViewRootHandler,而是要求调用者使用 UI 线程或显式 post/postInvalidate API。
void checkThread() {
Thread current = Thread.currentThread();
if (mThread != current) {
throwCalledFromWrongThreadException();
}
}
private CalledFromWrongThreadException newCalledFromWrongThreadException(boolean withInitStack) {
return new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views."
+ " Expected: " + mThread.getName()
+ " Calling: " + Thread.currentThread().getName(),
withInitStack ? mInitStack : null);
}此异常证明的是调用线程违反 ViewRootImpl 线程契约,不证明 MessageQueue 没有消息,也不证明 把同一调用包装成任意 Handler 消息就能安全执行;消息中的对象状态仍需在目标线程有效。
13. 验证输入
源码文件:frameworks/base/core/java/android/view/ViewRootImpl.java
第一组验证可检查 traversal 去重:在同一个 ViewRootImpl 上连续触发两次 scheduleTraversals(), 观察 mTraversalScheduled 只从 false 变 true 一次;第二次不会重复插入屏障。该验证依赖 ViewRootImpl 的测试可见性或源码级 instrumentation,不能仅用普通应用 API 断言内部 token。
初始:mTraversalScheduled = false
第一次 schedule:写 true,postTraversalBarrier,postVsyncCallback
第二次 schedule:发现 true,不再注册新的 callback第二组验证直接围绕消息载荷:调用 dispatchInvalidateDelayed(view, delay),然后在队列中 检查 what == MSG_INVALIDATE、obj == view;取消前调用 cancelInvalidate(view),断言匹配 消息不再存在。它证明的是 Handler 消息边界,不证明 View 已经完成下一次绘制。
源码文件:frameworks/base/core/java/android/view/ViewRootImpl.java
第三组验证使用 TransactionExecutor/ViewRoot 集成测试时,应分别安排窗口 resize、窗口 detach 和输入消息,断言:已 detach 的 ViewRoot 不继续处理 resize 状态;输入消息的 SomeArgs 被回收;trace 或 barrier 清理不会留下待执行 traversal。没有完整设备环境时, 这些结论应停留在源码分支和单元测试层,不外推为所有硬件渲染器行为。
14. 复查命令
源码文件:
frameworks/base/core/java/android/view/ViewRootImpl.javaframeworks/base/core/java/android/view/View.javaframeworks/base/core/java/android/view/Choreographer.javaframeworks/base/core/java/android/os/LegacyMessageQueue/MessageQueue.java
rg -n "class ViewRootHandler|MSG_INVALIDATE|MSG_RESIZED|MSG_DISPATCH_INPUT_EVENT" \
frameworks/base/core/java/android/view/ViewRootImpl.java
rg -n "scheduleTraversals|postTraversalBarrier|removeTraversalBarrier|doTraversal" \
frameworks/base/core/java/android/view/ViewRootImpl.java
rg -n "dispatchInvalidateDelayed|cancelInvalidate|dispatchInputEvent|dispatchResized" \
frameworks/base/core/java/android/view/ViewRootImpl.java
rg -n "requestLayout|postInvalidate|invalidateInternal" \
frameworks/base/core/java/android/view/View.java阅读一条 UI 更新路径时,顺序应是:View 的状态变化或窗口回调 → ViewRootImpl 状态/消息 → 同步屏障与 Choreographer → doTraversal() → performTraversals()。如果中间插入“Handler 直接执行 draw”,就已经偏离了当前源码。
15. 使用边界
ViewRootHandler 的价值在于把 ViewRootImpl 的窗口、输入和失效消息串到正确的 Looper; scheduleTraversals() 则把 layout/draw 请求合并到下一次 frame。它不提供跨线程修改 View 的 自动安全性,不让输入抢占正在运行的 callback,也不保证窗口 detach 后旧消息自动消失。
排查“调用了 requestLayout 但没有马上绘制”时,应依次检查:调用线程是否符合 ViewRootImpl 线程契约,mTraversalScheduled 是否已经为 true,屏障是否成功登记,Choreographer callback 是否仍在队列,doTraversal() 是否移除屏障,以及 performTraversals() 前的窗口状态是否 允许继续。这样才能把“没有请求”“等待帧”“消息被取消”和“窗口已失效”区分开。
