终章:60FPS 的奇迹——UI 渲染管线与 Choreographer 的舞蹈

2 阅读5分钟

Activity.onResume() 执行完毕,你以为界面就出现了?不,那只是逻辑上的“就绪”。
真正的魔法发生在接下来的 16.67 毫秒(即 1/60 秒)内。

在这极短的瞬间,CPU 必须完成测量、布局、绘制指令生成;GPU 必须完成光栅化;合成器必须将图层叠加并输出到屏幕。如果任何一步超时,用户就会看到卡顿(Jank)。

本篇作为本系列的终章,我们将深入 Android 图形系统的核心,揭秘 ChoreographerViewRootImplSurfaceFlinger 如何协同工作,创造出流畅的视觉体验。


第一步:时间的指挥家 —— Choreographer

Android 流畅度的秘密武器是 Choreographer(编舞者) 。它不与业务逻辑纠缠,只专注一件事:同步 VSync 信号

什么是 VSync?
显示屏以固定频率(如 60Hz)刷新。每次刷新前,硬件会发出一个垂直同步信号(VSync)。Choreographer 监听这个信号,确保所有的绘制操作都在新的刷新周期开始时启动,避免画面撕裂。

代码位置frameworks/base/core/java/android/view/Choreographer.java

// Choreographer 的核心循环逻辑
private void doFrame(long frameTimeNanos, int frame) {
    // 1. 计算是否掉帧 (如果当前时间比预期晚了很多)
    long intendedVsync = mVsyncTimeOffset + frame * mFrameIntervalNanos;
    if (frameTimeNanos > intendedVsync + mFrameIntervalNanos) {
        Log.w("Choreographer", "Skipped frames!"); // 掉帧警告
    }

    // 2. 【关键】按顺序执行四个阶段的回调
    // 顺序严格保证:输入 -> 动画 -> 布局/绘制 -> 提交
    
    doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);   // 处理触摸事件
    doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos); // 执行属性动画
    doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos); // 执行 measure, layout, draw
    doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);    // 提交绘制任务给 GPU
    
    // 3. 请求下一帧
    scheduleVsync(); 
}

核心机制:所有 UI 更新(invalidate(), requestLayout())本质上都是向 Choreographer 注册一个 CALLBACK_TRAVERSAL 任务,等待下一个 VSync 到来时统一执行。这避免了频繁的随机绘制。


第二步:视图树的遍历 —— Measure, Layout, Draw

当 Choreographer 触发 CALLBACK_TRAVERSAL 后,ViewRootImpl 开始驱动整个 View 树进行“体检”和“化妆”。

代码位置frameworks/base/core/java/android/view/ViewRootImpl.java

流程伪代码

// ViewRootImpl.performTraversals()
void performTraversals() {
    // 1. 测量 (Measure)
    // 自顶向下传递约束,自底向上返回尺寸
    // 问孩子:“你想要多大?”孩子:“我最多想要 100px。”
    mView.measure(measureWidth, measureHeight);

    // 2. 布局 (Layout)
    // 告诉孩子:“你坐在这里 (x, y),大小是 (w, h)”
    mView.layout(l, t, r, b);

    // 3. 绘制 (Draw)
    // 获取 Canvas,开始录制绘制指令
    Canvas canvas = mSurface.lockCanvas(); 
    try {
        mView.draw(canvas); 
    } finally {
        mSurface.unlockCanvasAndPost(canvas); // 提交缓冲区
    }
}

硬件视角

  • CPU 密集型:Measure 和 Layout 主要是 CPU 计算(递归遍历树,计算几何)。
  • 指令录制draw() 方法并没有直接画像素,而是将“画圆”、“填色”、“贴图”等指令录制到 DisplayList (RenderNode) 中。这是为了后续让 GPU 高效执行。

第三步:GPU 的介入 —— RenderThread 与 HWUI

在现代 Android 版本中,绘制工作不再完全由主线程承担,而是移交给了专门的 RenderThread

架构变化

  1. 主线程 (UI Thread) :负责计算 View 树结构,生成 DisplayList 指令集。
  2. 渲染线程 (RenderThread) :接收指令,调用 OpenGL ES 或 Vulkan API,将矢量指令转换为纹理(光栅化)。
  3. GPU:执行着色器程序,输出像素数据到 Framebuffer。

流程图:渲染管线 tongyi-mermaid-2026-03-11-233751.png

性能关键:如果主线程在 Measure/Layout 阶段耗时超过 8ms,留给 RenderThread 的时间就不够了,导致无法在下一个 VSync 前完成绘制,产生掉帧

🔹 第四步:最终的合成 —— SurfaceFlinger

App 绘制好了自己的窗口,但屏幕上还有状态栏、导航栏、其他 App 的悬浮窗。谁来把它们拼在一起?
答案是 SurfaceFlinger(运行在独立进程中的系统服务)。

工作流程

  1. 收集缓冲区:SurfaceFlinger 监听所有应用进程的 Graphic Buffer 队列。

  2. 合成 (Composition)

    • 如果是简单的叠加,GPU 直接合成。
    • 如果需要特效(如模糊、转场),可能使用 Hardware Composer (HWC) 硬件层。
  3. 输出:将最终合成的图像写入 Framebuffer,屏幕控制器读取并显示。

代码位置frameworks/native/services/surfaceflinger/SurfaceFlinger.cpp (C++)

// SurfaceFlinger 的主循环简化逻辑
void SurfaceFlinger::mainLoop() {
    while (true) {
        // 1. 等待 VSync 或新缓冲区到达
        waitForEvent(); 
        
        // 2. 锁定当前状态
        LockGuard lock(mStateLock);
        
        // 3. 遍历所有图层 (Layers)
        for (auto& layer : mLayers) {
            if (layer->hasNewBuffer()) {
                // 获取最新的 GraphicBuffer
                sp<GraphicBuffer> buf = layer->getNextBuffer();
                composeLayer(buf);
            }
        }
        
        // 4. 提交给 HWC (Hardware Composer) 或直接绘制
        mHwc->commit();
    }
}

系列终章总结:从硅片到像素的史诗

至此,我们的《Android 系统启动全景图》系列正式完结。让我们最后一次回顾这段从硬件复位到像素显示的壮丽旅程:

篇章阶段核心组件关键成就
第一篇硬件与Init 进程init, ueventd挂载文件系统,启动守护进程,解析 init.rc
第二篇Zygote 孵化app_process, Zygote预加载资源,Fork 机制 (COW),创建 Java 世界
第三篇SystemServerAMS, PMS, WMS启动核心服务,注册 Binder,系统就绪
第四篇App 启动Launcher, ActivityThread进程创建,Binder 通信,生命周期回调
第五篇UI 渲染Choreographer, GPU, SurfaceFlingerVSync 同步,渲染管线,图形合成,画面显示

全链路延迟分析
当你点击图标到看到界面,整个过程经历了:

  1. Input: 触摸屏中断 -> InputReader -> InputDispatcher (约 2-5ms)
  2. Logic: Launcher -> AMS -> Zygote -> App Process (约 100-500ms,取决于冷/热启动)
  3. Render: Measure/Layout/Draw -> GPU -> SurfaceFlinger (约 16ms/帧)

给开发者的启示

  • 启动优化:减少 Application 和 Activity onCreate 中的耗时操作,利用异步。
  • 流畅度优化:避免在主线程进行 IO 或复杂计算,确保在 16ms 内完成 Traversal。
  • 内存优化:理解 Zygote 的 COW 机制,避免过早修改共享内存导致内存膨胀。

结语

Android 系统是一个宏伟的工程奇迹。它融合了 Linux 内核的稳健、Java 虚拟机的灵活、Binder 机制的高效以及图形管线的精湛。

希望这个系列能帮你建立起对 Android 系统的全局观。下次当你看到手机开机动画,或者流畅地滑动列表时,希望你能在脑海中浮现出这些进程、线程和信号交织共舞的画面。

技术之路无止境,探索永不止步。