流程图
先上图,下图描述了调用invalidate()
后各个阶段与核心组件协作:
提交TRAVERSAL任务] end subgraph Phase2[阶段二: 系统层 - 等待与响应VSync信号] E --> F[Choreographer订阅下一次VSync] F --> G{等待VSync信号到达} G --> H[FrameDisplayEventReceiver.onVsync] H --> I[发送异步消息到MessageQueue] I --> J[因同步屏障, 消息被优先处理] J --> K[执行Choreographer.doFrame] end subgraph Phase3[阶段三: 执行绘制回调] K --> L[按顺序执行Callbacks] L --> M[INPUT回调] M --> N[ANIMATION回调] N --> O[TRAVERSAL回调] O --> P[执行ViewRootImpl.PerformTraversals] end subgraph Phase4[阶段四: 遍历视图树并绘制] P --> Q[PerformMeasure: 测量] Q --> R[PerformLayout: 布局] R --> S[PerformDraw: 绘制] S --> T[View.draw: 发起整个视图树的绘制] T --> U[递归调用子View的draw] U --> V[最终调用你的View.onDraw方法] end subgraph Phase5[阶段五: 渲染与显示] V --> W[UI线程: 录制绘制命令到DisplayList] W --> X[将命令同步至RenderThread渲染线程] X --> Y[RenderThread: GPU栅格化
将矢量命令转换为像素数据] Y --> Z[操作Back Buffer] Z --> A1{栅格化在16.6ms内完成?} A1 -- 是 --> B1[交换Swap Buffer] A1 -- 否 --> C1[丢帧 Jank] B1 --> D1[SurfaceFlinger合成所有Layer] D1 --> E1[屏幕从Frame Buffer读取数据显示] end Phase1 --> Phase2 --> Phase3 --> Phase4 --> Phase5
源码
主要源码如下(基于API32):
java
//View.java
public void invalidate() {
invalidate(true);
}
public void invalidate(boolean invalidateCache) {
invalidateInternal(0, 0, mRight - mLeft, mBottom - mTop, invalidateCache, true);
}
void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache, boolean fullInvalidate) {
//...忽略其他代码...
// Propagate the damage rectangle to the parent view.
final AttachInfo ai = mAttachInfo;
final ViewParent p = mParent;
if (p != null && ai != null && l < r && t < b) {
final Rect damage = ai.mTmpInvalRect;
damage.set(l, t, r, b);
//继续向上找,找到父类ViewGroup
p.invalidateChild(this, damage);
}
}
在invalidateInternal()中,会向上找父ViewGroup#invalidateChild()
,并将dirty区域向上传。
java
//ViewGroup.java
public final void invalidateChild(View child, final Rect dirty) {
final AttachInfo attachInfo = mAttachInfo;
ViewParent parent = this;
do {
//忽略其他代码
parent = parent.invalidateChildInParent(location, dirty);
}
} while (parent != null);
}
}
public ViewParent invalidateChildInParent(final int[] location, final Rect dirty) {
if ((mPrivateFlags & (PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID)) != 0) {
//合并脏区域,并且将脏区域的坐标从子View转换成父View的相对坐标
dirty.offset(location[CHILD_LEFT_INDEX] - mScrollX,
location[CHILD_TOP_INDEX] - mScrollY);
if ((mGroupFlags & FLAG_CLIP_CHILDREN) == 0) {
dirty.union(0, 0, mRight - mLeft, mBottom - mTop);
}
//返回父View
return mParent;
}
return null;
}
invalidateChild()会在循环中先调用自身的invalidateChildInParent()合并dirty脏区域并返回父mParent,只要mParent不为空会一直循环遍历,直到找到根节点ViewRootImpl并调用其invalidateChildInParent()方法:
typescript
//ViewRootImpl.java
public void invalidateChild(View child, Rect dirty) {
invalidateChildInParent(null, dirty);
}
接着会调用invalidateChildInParent() -> invalidateRectOnScreen() -> scheduleTraversals()
:
java
final Choreographer mChoreographer;
void scheduleTraversals() {
//设置为异步消息,优先执行
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
//注册Vsync回调
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
}
//Vsync后执行的回调
final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
scheduleTraversals()
中通过Choreographer
注册了CALLBACK_TRAVERSAL
回调,mTraversalRunnable
就是要执行的Runnable,其run()方法中会执行doTraversal()
:
scss
void doTraversal() {
//忽略其他代码
performTraversals();
}
终于到了我们熟悉的ViewRootImpl#performTraversals()
方法了,后续的measure、layout、draw
都在这里执行了,等绘制完毕,GPU会将数据栅格化并存入Buffer。然后就等待Vsync信号返回后屏幕展示我们绘制的东西了。
阶段详解
阶段一:应用层 - 标记脏区域与调度
View.invalidate()
被调用 :表示这个View
的内容已经失效("脏"了),需要重新绘制,invalidate()
只是计划一个重绘事件,并不会立即触发绘制。- 递归向上标记 :
invalidate()
方法内部并不会直接进行绘制。它会沿着View的父级一路递归向上 ,直到ViewRootImpl
(整个视图树的根)。这个过程中,各级父View会记录下需要重绘的区域(Dirty区域),以便优化,只绘制必要的部分。 - 调度遍历 :当调用链到达
ViewRootImpl
时,会调用它的scheduleTraversals()
方法。这个方法的核心作用是:向 Choreographer 提交一个任务 ,请求在下一帧执行完整的"遍历"(Traversal),即测量、布局、绘制。它通过Choreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null)
实现。
阶段二:系统层 - 等待与响应VSync信号
- 订阅VSync :
Choreographer
收到你的TRAVERSAL
任务后,会将其放入一个专门的队列(CallbackQueue
)中等待执行。然后,它会通过FrameDisplayEventReceiver
向系统底层订阅下一次VSync信号。 - VSync信号到达 : 大约16.6ms后(在60Hz设备上),硬件发出VSync信号。
Choreographer
的FrameDisplayEventReceiver
会接收到这个信号,并触发它的onVsync()
方法。 - 派发到UI线程 :
onVsync()
方法会向主线程的MessageQueue
发送一个异步消息(这是一个高优先级的消息,即使此时队列中有其他同步消息,它也会被优先处理,主要是因为设置了"同步屏障",即通过Handler发送的是异步消息。
阶段三:执行绘制回调 - doFrame()
- 执行
Choreographer.doFrame()
:主线程处理这条异步消息,执行Choreographer.doFrame(long frameTimeNanos)
方法。这是真正开始构建一帧的起点 。doFrame()
会按严格且固定的顺序 处理三种类型的回调: *CALLBACK_INPUT
:处理输入事件。 *CALLBACK_ANIMATION
:执行动画回调,更新所有动画的属性值。 *CALLBACK_TRAVERSAL
:这就是我们提交的任务所在队列。在这里,会从队列中取出之前放入的mTraversalRunnable
并执行。 - 执行
performTraversals()
:mTraversalRunnable
的run()
方法很简单,就是调用ViewRootImpl
的performTraversals()
。这是整个View系统中最核心、最复杂的方法,它负责协调: *performMeasure()
:测量View树中各个View的尺寸。 *performLayout()
:布局,确定每个View在屏幕上的位置。 *performDraw()
:绘制
阶段四:遍历视图树 - 触发onDraw()
- 发起绘制 :
performDraw()
方法会最终调用View.draw()
方法(注意不是onDraw()
),开始整个视图树的绘制流程。 - 递归调用与触发
onDraw()
:View.draw()
方法会完成自己的背景绘制等内容,然后调用onDraw(Canvas canvas)
方法。onDraw()
方法中的Canvas
对象在这里进行自定义绘制(画线、文字、位图等)。- 之后,
View.draw()
方法会继续分发,递归地调用所有子View的draw()
方法,从而让整个视图树都得到绘制。
- 录制绘制命令 :需要注意的是,在硬件加速开启 的情况下(默认),在
onDraw()
中调用Canvas
的绘制方法(如drawRect
,drawPath
)并不会立即执行 ,而是被录制为一系列绘制命令(Display List),稍后交由另一个线程处理。
阶段五:渲染与显示 - 像素上屏
- 同步到渲染线程 : UI线程的绘制命令录制完成后,会将这些命令同步 给一个独立的
RenderThread
(渲染线程) - GPU栅格化 :
RenderThread
将这些矢量绘制命令提交给 GPU 。GPU在 Back Buffer(后台缓冲区) 上执行栅格化 操作,将矢量命令(如"在x,y位置画一个红色的圆")转换成最终的像素数据。 - 交换缓冲区 :当一帧的栅格化完成后,GPU会执行一个
swap
操作,将已经准备好的 Back Buffer 和当前正在显示的 Frame Buffer(前台缓冲区) 进行交换。 - SurfaceFlinger 合成与显示 :系统服务
SurfaceFlinger
会收集所有应用程序窗口交换后的缓冲区。它根据窗口的Z-order、透明度等信息,将它们合成 为最终屏幕应该显示的图像。当下一个 VSync 信号到来时,屏幕就会从最终合成好的 Frame Buffer 中读取像素数据,你将看到最新的画面。
总结
调用 invalidate()
不是一个即时命令,而是一个预约请求。它触发了一个漫长的流水线:
invalidate()
-> 标记脏区域 -> 调度 Traversal
-> 订阅VSync -> 等待信号 -> doFrame()
-> performTraversals()
-> draw()
-> onDraw()
-> 录制命令 -> 提交渲染线程 -> GPU栅格化 -> 交换缓冲区 -> SurfaceFlinger合成 -> 屏幕显示
这个过程任何一步(尤其是UI线程的 doFrame
或GPU的栅格化)超过16.6ms,就会导致丢帧。