一、为什么写这篇文章?
Android 渲染是一个跨层协作的复杂系统:应用层负责「画什么」,系统层负责「怎么摆」,硬件层负责「亮起来」。很多开发者能背出「16ms 一帧」,但追问「这 16ms 里系统到底在干什么」,往往就卡壳了。
这篇文章的目标是把整条链路串成一根线,从应用层的 Choreographer 一路穿透到系统层的 SurfaceFlinger,再到硬件层的 HWC/DRM。读完之后,你会理解:
- 为什么
invalidate()不是立即重绘? - 为什么
onDraw()里不能创建对象? - 为什么 Triple Buffering 能减少卡顿?
- 为什么 SurfaceView 比 TextureView 更省电?
二、整体架构:一张图看清全局

核心结论:
- 应用层负责「画什么」(Canvas 绘制、View 遍历)
- 系统层负责「怎么摆」(窗口层级、Surface 合成)
- 硬件层负责「亮起来」(物理像素刷新)
三者通过 BufferQueue 解耦,通过 VSync 信号同步。
三、应用层:Choreographer 的节拍器
3.1 为什么需要 Choreographer?
Android 的显示刷新率是固定的(60Hz、90Hz、120Hz)。如果应用「想画就画」,很可能画到一半屏幕就开始刷新,导致画面撕裂(Tearing)。
Choreographer 的作用就是让应用的绘制节奏和屏幕的刷新节奏对齐。
3.2 Choreographer 的源码核心
java
// frameworks/base/core/java/android/view/Choreographer.java
public final class Choreographer {
// 单例,每个线程一个(但主线程的才是渲染相关的)
private static final ThreadLocal<Choreographer> sThreadInstance =
new ThreadLocal<Choreographer>() {
@Override
protected Choreographer initialValue() {
// 获取当前线程的 Looper,创建 Choreographer
Looper looper = Looper.myLooper();
if (looper == null) {
throw new IllegalStateException(...);
}
return new Choreographer(looper, VSYNC_SOURCE_APP);
}
};
// 核心:申请下一个 VSync 信号
public void postFrameCallback(FrameCallback callback) {
postCallbackInternal(CALLBACK_ANIMATION, callback, null);
}
// 收到 VSync 后的处理
void doFrame(long frameTimeNanos, int frame) {
// ... 时间戳校验
// 阶段 1:处理输入事件(INPUT)
doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);
// 阶段 2:执行动画(ANIMATION)
doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);
// 阶段 3:遍历 View 树(TRAVERSAL)
// 这里会触发 measure -> layout -> draw
doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);
// 阶段 4:绘制(DRAW)
doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);
}
}
3.3 四个 Callback 类型的含义

| Callback 类型 | 执行内容 | 典型耗时 | 卡顿根因 |
|---|---|---|---|
CALLBACK_INPUT |
分发触摸/按键事件 | 1-3ms | 事件处理耗时(如点击后同步加载数据) |
CALLBACK_ANIMATION |
执行 ValueAnimator、ObjectAnimator | 1-5ms | 动画回调中做重计算 |
CALLBACK_TRAVERSAL |
measure -> layout -> draw |
5-15ms | 布局嵌套过深、onMeasure 耗时 |
CALLBACK_COMMIT |
提交绘制结果到 Surface | 1-3ms | 大量绘制命令(如复杂 Path) |
3.4 VSync 信号的传递路径
rust
HWC (Hardware Composer)
│
│ 产生 VSync 信号(硬件中断)
▼
SurfaceFlinger::onVSyncReceived()
│
│ 通过 Binder IPC 发送 VSync 事件
▼
Choreographer::FrameDisplayEventReceiver::onVSync()
│
│ 发送 Message 到主线程 Looper
▼
Choreographer::doFrame()
│
│ 依次执行 INPUT -> ANIMATION -> TRAVERSAL -> DRAW
▼
ViewRootImpl::performTraversals()
关键点 :VSync 信号从硬件到应用,经历了 HWC -> SurfaceFlinger -> Binder IPC -> 主线程 Handler ,每一步都有延迟。所以应用实际可用的绘制时间 不到 16ms ,通常只有 12-14ms。
四、View 绘制:从 invalidate 到 Canvas
4.1 invalidate() 的调用链
java
// 开发者调用
view.invalidate();
// -> View.java
public void invalidate() {
invalidate(true);
}
void invalidate(boolean invalidateCache) {
// 把自己标记为「需要重绘」
// 递归向上通知父 View
final ViewParent parent = mParent;
if (parent != null) {
parent.invalidateChild(this, null);
}
}
// -> ViewGroup.java
public final void invalidateChild(View child, final Rect dirty) {
// 合并脏区域,继续向上传递
// 最终到达 ViewRootImpl
}
// -> ViewRootImpl.java
void invalidate() {
mDirty.set(0, 0, mWidth, mHeight);
if (!mWillDrawSoon) {
scheduleTraversals(); // 关键:申请下一次 VSync
}
}
核心逻辑 :invalidate() 不是立即重绘,而是标记脏区域并申请 VSync 。真正的绘制发生在下一个 doFrame() 的 TRAVERSAL 阶段。
4.2 performTraversals() 的三阶段
java
// ViewRootImpl.java
private void performTraversals() {
// 阶段 1:Measure(测量)
// 从根 View 递归调用 child.measure()
// 确定每个 View 的宽高
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
// 阶段 2:Layout(布局)
// 从根 View 递归调用 child.layout()
// 确定每个 View 的位置
performLayout(lp, desiredWindowWidth, desiredWindowHeight);
// 阶段 3:Draw(绘制)
// 创建 Canvas,从根 View 递归调用 child.draw()
performDraw();
}
4.3 draw() 方法的 6 步
java
// View.java
public void draw(Canvas canvas) {
// Step 1: 绘制背景
drawBackground(canvas);
// Step 2: 保存 Canvas 状态
final int saveCount = canvas.save();
// Step 3: 绘制内容(onDraw)
onDraw(canvas);
// Step 4: 绘制子 View(dispatchDraw)
dispatchDraw(canvas);
// Step 5: 绘制装饰(如滚动条)
onDrawForeground(canvas);
// Step 6: 恢复 Canvas 状态
canvas.restoreToCount(saveCount);
}
为什么 onDraw() 里不能创建对象?
因为 onDraw() 在每一帧都会被调用(如果 View 在动画或滚动中)。创建对象会触发 GC,而 GC 的 Stop-The-World 会阻塞主线程,导致掉帧。正确做法是用成员变量缓存 Paint、Path、Rect 等对象。
五、Surface 与 BufferQueue:跨进程的数据通道
5.1 为什么需要 Surface?
View 的绘制最终要落到屏幕上,但应用进程不能直接访问屏幕。Android 使用 Surface 作为抽象层,应用向 Surface 绘制,系统从 Surface 读取并合成。
5.2 BufferQueue 的三缓冲机制

三缓冲的意义:
- 双缓冲:应用绘制一个 Buffer,SF 合成另一个。如果应用绘制超时,SF 只能重复显示旧帧(卡顿)。
- 三缓冲:多一个 Buffer 作为「备胎」。应用绘制超时时,SF 可以用第三个 Buffer 继续合成,减少卡顿感知。
Android 4.1 引入 Project Butter 后,默认就是三缓冲。
5.3 从 unlockCanvasAndPost 到 SurfaceFlinger
java
// 应用层:绘制完成后提交
SurfaceHolder.unlockCanvasAndPost(canvas);
// -> Surface.java (JNI 调用)
public void unlockCanvasAndPost(Canvas canvas) {
// ... 检查 Canvas 状态
unlockSwCanvasAndPost(canvas);
}
// -> android_view_Surface.cpp (Native 层)
static void nativeUnlockCanvasAndPost(JNIEnv* env, jclass clazz,
jlong nativeObject, jobject canvasObj) {
// 获取 ANativeWindow
sp<Surface> surface(reinterpret_cast<Surface *>(nativeObject));
// 解锁 Buffer 并提交到 BufferQueue
status_t err = surface->unlockAndPost();
// 通知 SurfaceFlinger 有新数据
// 通过 Binder 发送 INVALIDATE 消息
}
六、SurfaceFlinger:系统的「合成器」
6.1 SurfaceFlinger 的职责
SurfaceFlinger 是 Android 系统的核心系统服务,负责:
- 接收来自各个应用的图形 Buffer
- 合成(Compose)所有 Layer 为一个最终图像
- 送显(Present)到物理屏幕
6.2 Layer 的概念
每个 Window 对应一个 Layer ,每个 Layer 对应一个 BufferQueue。
6.3 合成方式:GPU 合成 vs HWC 合成

HWC (Hardware Composer) 是显示芯片的硬件模块,可以直接合成多个 Layer,不需要 GPU 参与。它的优势:
- 省电:不需要唤醒 GPU
- 低延迟:硬件直通,没有 GPU 渲染的排队
但 HWC 有限制:支持的 Layer 数量有限(通常 4-8 个),不支持复杂混合模式(如 Porter-Duff)。
6.4 SurfaceFlinger 的主循环
cpp
// frameworks/native/services/surfaceflinger/SurfaceFlinger.cpp
void SurfaceFlinger::run() {
while (true) {
// 1. 等待 VSync 信号或事件
waitForEvent();
// 2. 处理事务(如应用提交新 Buffer、窗口属性变化)
processTransactionRequests();
// 3. 收集所有需要合成的 Layer
refreshLayers();
// 4. 执行合成
// - 如果有 HWC 支持,走 HWC 合成
// - 否则,用 GPU 合成到 Framebuffer
compose();
// 5. 送显
// - 调用 HWC::presentDisplay() 或 DRM ioctl
present();
}
}
七、硬件层:从 Framebuffer 到像素
7.1 DRM/KMS 架构
现代 Android 设备使用 DRM (Direct Rendering Manager) 和 KMS (Kernel Mode Setting) 管理显示硬件。
7.2 送显时序:从 VBlank 到像素发光

VBlank (Vertical Blank) 是屏幕两行扫描之间的空隙。在这个时间段内切换 Framebuffer,用户不会看到「一半旧一半新」的画面。
八、卡顿的根因定位:从现象到代码
8.1 卡顿的三种类型
| 类型 | 表现 | 根因 | 检测方法 |
|---|---|---|---|
| 渲染卡顿 | 滑动掉帧、动画不流畅 | 主线程绘制耗时 > 16ms | Systrace / Perfetto 看 doFrame |
| 合成卡顿 | 所有 App 都卡 | SurfaceFlinger 合成耗时 | dumpsys SurfaceFlinger |
| 硬件卡顿 | 特定场景卡(如玩游戏) | GPU 满载、温度降频 | GPU Profiling / 温度监控 |
8.2 Systrace 解读:找到卡顿帧

8.3 常见卡顿根因与修复
| 根因 | Systrace 特征 | 修复方案 |
|---|---|---|
| 布局嵌套过深 | performMeasure 耗时 8ms+ |
ConstraintLayout 替代嵌套 LinearLayout |
| 过度绘制 | draw() 耗时 5ms+,多次 onDraw |
开启「调试 GPU 过度绘制」优化 |
| 主线程 IO | doFrame 中有 FileInputStream.read |
异步线程 + 回调 |
| Bitmap 大图 | drawBitmap 耗时 10ms+ |
采样压缩、inBitmap 复用 |
| RecyclerView 复用问题 | onBindViewHolder 耗时 5ms+ |
ViewHolder 模式、异步加载图片 |
| 动画阻塞 | Choreographer#Animation 耗时 8ms+ |
动画回调中避免重计算 |
九、深度问答
Q1: 为什么 Android 要引入 Triple Buffering?
Triple Buffering 解决的是**「CPU 等待 GPU」**的问题。
在 Double Buffering 下:
- Buffer A:显示中
- Buffer B:应用绘制中
如果应用绘制耗时(CPU)> 16ms,比如用了 20ms:
- 16ms 时 VSync 到了,Buffer B 还没画完
- SurfaceFlinger 只能继续显示 Buffer A(重复帧,用户感知卡顿)
- 20ms 时 Buffer B 画完,但下一个 VSync 在 33ms
- 所以 CPU 从 20ms 到 33ms 一直在空等
在 Triple Buffering 下:
- Buffer A:显示中
- Buffer B:应用绘制中(20ms 完成)
- Buffer C:空闲
20ms 时 Buffer B 画完,SurfaceFlinger 可以立即合成到 Buffer C,CPU 不需要等待 VSync,可以立即开始画下一帧。这样提高了 CPU 利用率,减少了空闲等待。
但代价是内存增加(多一个 Buffer,可能 8MB+),所以低端设备可能还是 Double Buffering。
Q2: SurfaceView 和 TextureView 有什么区别?
| 特性 | SurfaceView | TextureView |
|---|---|---|
| 渲染层 | 独立的 Surface,在 Window 之外 | 和普通 View 一样,在 View 树中 |
| 合成方式 | 直接通过 HWC overlay,不经过 GPU | 需要 GPU 合成到 Layer |
| 性能 | 更高(零拷贝、HWC 直通) | 较低(需要 GPU 纹理采样) |
| 灵活性 | 低(不能透明、不能旋转、不能动画) | 高(支持 Alpha、Matrix 变换、动画) |
| 适用场景 | 视频播放、相机预览 | 需要灵活变换的 Surface 内容 |
SurfaceView 的 Surface 是独立 Layer,SurfaceFlinger 可以直接用 HWC 合成,不需要 GPU 参与。而 TextureView 的内容需要先渲染到 GPU 纹理,再作为普通 View 的一部分合成,多了一次 GPU 纹理采样。
Q3: onDraw() 里为什么不能做动画?
onDraw() 的调用频率不是固定的,它取决于:
- 是否调用了
invalidate() - Choreographer 的 VSync 调度
- 系统负载
如果做动画(如逐帧改变位置),需要主动触发重绘 ,但 onDraw() 本身是被动调用的。正确做法是:
- 用
ValueAnimator+Choreographer.postFrameCallback,在回调中更新状态并invalidate() - 或者用
SurfaceView+ 独立线程主动绘制
Q4: Android 12 的 SplashScreen API 是怎么实现的?
SplashScreen 本质上是一个特殊 Window ,由 WindowManagerService 在 App 进程启动时创建。
流程:
ActivityThread.handleLaunchActivity()调用performLaunchActivity()- 在
Activity.onCreate()之前,PhoneWindow创建SplashScreenView SplashScreenView是一个独立 Layer,Z-Order 在 App Window 之上- 当 App 第一帧绘制完成(
reportDrawn()),WMS 发送消息移除 SplashScreen Layer - 移除动画是 WMS 控制的
SurfaceAnimator,通过Transaction改变 Layer Alpha
关键点:SplashScreen 不是 App 自己画的,是系统进程画的,所以即使 App 主线程卡住,SplashScreen 也能正常显示。
十、总结:一张图记住整条链路

核心记忆点:
- VSync 是节拍器:Choreographer 靠它对齐绘制节奏
- Surface 是画布:应用画完提交给系统
- SurfaceFlinger 是合成器:把多个应用的 Layer 拼成一张图
- HWC 是加速器:能用硬件就不用 GPU,省电省资源
- 卡顿在应用层:系统层通常不是瓶颈,主线程耗时才是