从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 的渲染系统是一个精密而高效的协同体系,每一个环节都经过精心设计,缺一不可。

相关推荐
alexhilton4 分钟前
运行时着色器实战:实现元球(Metaballs)动效
android·kotlin·android jetpack
從南走到北36 分钟前
JAVA国际版东郊到家同城按摩服务美容美发私教到店服务系统源码支持Android+IOS+H5
android·java·开发语言·ios·微信·微信小程序·小程序
观熵3 小时前
Android 相机系统全景架构图解
android·数码相机·架构·camera·影像
Huntto3 小时前
在Android中使用libpng
android
雨白5 小时前
Android 自定义 View:彻底搞懂 Xfermode 与官方文档陷阱
android
_小马快跑_6 小时前
Android | 视图渲染:从invalidate()到屏幕刷新的链路解析
android
Monkey-旭8 小时前
Android 定位技术全解析:从基础实现到精准优化
android·java·kotlin·地图·定位
树獭非懒10 小时前
Android 媒体篇|吃透 MediaSession 与 MediaController
android·架构
一起搞IT吧11 小时前
高通Camx hal进程CSLAcquireDeviceHW crash问题分析一:CAM-ICP FW response timeout导致
android·图像处理·数码相机