从VSync心跳到SurfaceFlinger合成:拆解 Choreographer与Display刷新流程

流畅的界面渲染是用户体验的核心要素之一。屏幕上的每一个像素从数据到最终显示,背后都经历了一场精密的协同作战。本文将解析 Android 渲染系统的核心机制,揭示 VSync 信号、Choreographer、SurfaceFlinger 等关键组件如何协同工作,确保每一帧都能流畅呈现。

整体流程图

从应用层的invalidate、postInvalidateOnAnimation()开始触发刷新,到最终屏幕完成一帧的绘制并展现在屏幕上,核心流程如下所示:

核心角色解析

1. Vsync 垂直信号

Vsync 是一个由硬件产生的定时中断信号,可以理解为总指挥官的 "发令枪",是整个渲染流水线的节奏协调者。

以固定的频率(如60Hz每16.6ms一次)发出信号,强行将CPU、GPU和屏幕刷新这三个原本独立运行的组件的起始时间拉到同一起跑线。确保屏幕只在缓冲区内容完全准备好后才开始读取,避免同时读取和写入同一个缓冲区造成的画面撕裂;

2. Choreographer 编舞者

Choreographer 可以看做 视图的 "指挥家" 。它是连接应用层和系统底层VSync信号的核心枢纽,负责接收各种绘制请求,并在正确的时机调度执行。通过 postCallback() 方法接收应用层的三种异步更新请求:

  1. CALLBACK_INPUT:输入事件;
  2. CALLBACK_ANIMATION:动画计算回调,计算并更新View的动画属性(如translationX、alpha等)。这是动画流畅的关键。
  3. CALLBACK_TRAVERSAL:视图树绘制(Measure, Layout, Draw),这是最重量级的步骤,ViewRootImplperformTraversals() 会在这里被调用,最终触发整个View树的绘制,生成DrawOp命令列表。

当有回调需要处理时,通过 FrameDisplayEventReceiver 向系统订阅下一次VSync信号。在收到VSync信号后,在 doFrame() 中按照上述固定顺序执行所有收集到的回调。

此外,开发者还可以主动通过 Choreographer 添加回调,在两次回调中通过前后时间差来算出是否产生丢帧现象,如下实现:

kotlin 复制代码
Choreographer.getInstance().postFrameCallback(object : FrameCallback {
    var lastFrameTimeNanos = 0L
    var currentFrameTimeNanos = 0L
    override fun doFrame(frameTimeNanos: Long) {
        //注册的Vsync回调
        log("doFrame")
        if (lastFrameTimeNanos == 0L) {
            lastFrameTimeNanos = frameTimeNanos
        }
        currentFrameTimeNanos = frameTimeNanos
        val diffMs = (currentFrameTimeNanos - lastFrameTimeNanos).nanoseconds.inWholeNanoseconds
        if (diffMs > 16.6f) {
            val droppedCount = (diffMs / 16.6)
            log("发生丢帧")
        }
        Choreographer.getInstance().postFrameCallback(this);
    }
})

上述代码中需要注意,Choreographer 必须主动订阅Vsync信号才能接收。无UI更新时则不订阅就不会收到回调,节省资源。

3. Choreographer.doFrame() 执行时机

doFrame() 是一帧工作的核心 。此时是真正开始构建一帧画面的起点 。具体路径是:VSync -> FrameDisplayEventReceiver.onVsync() -> 发送异步消息 -> MessageQueue -> run() -> doFrame()

ini 复制代码
void doFrame(long frameTimeNanos, int frame,
        DisplayEventReceiver.VsyncEventData vsyncEventData) {
    final long startNanos;
    final long frameIntervalNanos = vsyncEventData.frameInterval;
    try {
        synchronized (mLock) {
            //1、掉帧计算
            long intendedFrameTimeNanos = frameTimeNanos;
            startNanos = System.nanoTime();
            final long jitterNanos = startNanos - frameTimeNanos;
            if (jitterNanos >= frameIntervalNanos) {
                final long skippedFrames = jitterNanos / frameIntervalNanos;
                final long lastFrameOffset = jitterNanos % frameIntervalNanos;
                frameTimeNanos = startNanos - lastFrameOffset;
            }
            mFrameInfo.setVsync(intendedFrameTimeNanos, frameTimeNanos, vsyncEventData.id,
                    vsyncEventData.frameDeadline, startNanos, vsyncEventData.frameInterval);
            mFrameScheduled = false;
            mLastFrameTimeNanos = frameTimeNanos;
            mLastFrameIntervalNanos = frameIntervalNanos;
            mLastVsyncEventData = vsyncEventData;
        }
        //2、执行各种回调
        doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos, frameIntervalNanos);
        doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos, frameIntervalNanos);
        doCallbacks(Choreographer.CALLBACK_INSETS_ANIMATION, frameTimeNanos,
                frameIntervalNanos);
        doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos, frameIntervalNanos);
        doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos, frameIntervalNanos);
    } finally {
        AnimationUtils.unlockAnimationClock();
    }
}

上述源码来自API 32,可以看到在doFrame()主要执行了2个操作,1处是进行了掉帧计算 :首先检查执行时间是否超过了帧期限(16.6ms),计算并记录掉帧情况。2处是执行回调:按固定顺序执行队列任务,主要的是INPUT、ANIMATION、TRAVERSAL回调。

4. SurfaceFlinger

SurfaceFlinger 负责最终的合成与提交显示到驱动 。它是一个运行在系统进程中的守护服务。 它可以根据Z-order、透明度、位置等信息,将所有可见图层的最终缓冲区 合成在一起,产生整个屏幕的最终像素数据;此外还会将合成好的帧数据提交给屏幕显示驱动,最终驱动屏幕刷新。

5. 屏幕 Display

Display 负责结果的最终呈现 。它会以固定的物理频率(如60Hz)从Frame Buffer中逐行读取像素数据并点亮屏幕上的像素点;不过Display 的刷新动作同样受VSync信号同步,确保只在SurfaceFlinger完成合成送显后,才从缓冲区读取新数据。

6. RenderThread 渲染线程

RenderThread 是异步GPU命令执行者。一个在应用进程内,但是一个独立的专有线程。负责任务:

  • 异步绘制 :接收UI线程录制好的绘制命令(DisplayList),并在后台交由GPU执行,不阻塞UI线程。
  • 栅格化:将UI线程传来的矢量绘制命令(如画圆、绘制纹理)转换为最终的像素信息,这是一个极其耗时的过程,由GPU完成。
  • 缓冲区管理 :负责管理BufferQueue,将渲染好的帧放入队列,并通知SurfaceFlinger来取。它直接处理了Back Buffer的渲染和交换(swap)操作。

总结:一帧的协作流程

  1. 应用通过 invalidate 或动画更新等指令发出任务给 ChoreographerChoreographer 订阅下一次 VSync 信号同步节奏;
  2. VSync 信号到达时,Choreographer.doFrame()开始执行,在 doFrame() 中,按顺序处理动画计算视图绘制,在UI线程生成绘制命令。
  3. UI线程将命令同步给 RenderThread ,由它异步地交给GPUBack Buffer 上进行栅格化 。渲染完成后,RenderThread 将缓冲区交换到前台,SurfaceFlinger 获取所有App的最终缓冲区并进行合成
  4. 下一个 VSync 信号到来时(注意是下一个Vsync信号),屏幕 从准备好的 Frame Buffer 中读取数据,完成显示。

任何一个环节超时(通常是UI线程的 doFrame 或RenderThread的栅格化),都会导致交换失败,SurfaceFlinger 只能继续使用旧帧数据,用户就看到了掉帧现象。

从 Choreographer 的调度到 SurfaceFlinger 的合成,从 UI 线程的计算到 RenderThread 的渲染这一完整流程,Android 的渲染系统是一个精密而高效的协同体系,每一个环节都经过精心设计,缺一不可。

相关推荐
有位神秘人20 分钟前
Android中Notification的使用详解
android·java·javascript
·云扬·30 分钟前
MySQL Binlog落盘机制深度解析:性能与安全性的平衡艺术
android·mysql·adb
独自破碎E2 小时前
【BISHI9】田忌赛马
android·java·开发语言
代码s贝多芬的音符3 小时前
android 两个人脸对比 mlkit
android
darkb1rd5 小时前
五、PHP类型转换与类型安全
android·安全·php
gjxDaniel5 小时前
Kotlin编程语言入门与常见问题
android·开发语言·kotlin
csj505 小时前
安卓基础之《(22)—高级控件(4)碎片Fragment》
android
峥嵘life6 小时前
Android16 【CTS】CtsMediaCodecTestCases等一些列Media测试存在Failed项
android·linux·学习
stevenzqzq7 小时前
Compose 中的状态可变性体系
android·compose
似霰7 小时前
Linux timerfd 的基本使用
android·linux·c++