0x00 Android 渲染机制解析

在做 Android 性能优化或卡顿分析 时,经常会遇到一些问题:

  • 为什么 UI 会掉帧?
  • GPU 为什么会成为瓶颈?
  • 为什么有时 SurfaceFlinger 会导致延迟?

这些问题的根源,往往在于 Android 渲染 pipeline 的理解不完整

Android 并不是简单地把 UI 直接画到屏幕上,而是通过一套 Buffer + Layer + 合成机制完成显示。

简单来说:

css 复制代码
App 负责生产图像
SurfaceFlinger 负责合成图像
Display 负责扫描显示

下面我们从 draw → GPU → 合成 → 屏幕显示 的完整链路来分析。


一、从 draw 到 GPU 渲染

在 Android 中,View 的绘制并不会直接产生屏幕像素,而是先记录绘制指令

当系统触发一帧渲染时,UI 线程会执行:

scss 复制代码
ViewRootImpl.performTraversals()

最终调用:

css 复制代码
View.draw(Canvas)

draw() 做的事情并不是直接画图,而是:

scss 复制代码
draw()
   │
   ▼
RecordingCanvas
   │
   ▼
RenderNode
   │
   ▼
DisplayList

也就是说:

draw 阶段只是把绘制操作记录成 DisplayList(绘制指令列表)

例如:

复制代码
drawRect
drawBitmap
drawText

这些命令会被保存下来,随后交给 RenderThread 执行真正的 GPU 渲染。


GPU 渲染流程

RenderThread 会读取 DisplayList,然后调用 OpenGL/Vulkan 进行 GPU 渲染:

复制代码
UI Thread
   │
   ▼
DisplayList

RenderThread
   │
   ▼
OpenGL / Vulkan
   │
   ▼
GPU Pipeline
   │
   ▼
GraphicBuffer

GPU 渲染完成后,会把结果写入:

复制代码
GraphicBuffer

这是一块可以被 GPU、SurfaceFlinger、Display 硬件共享访问的图像内存。


二、BufferQueue 与 SurfaceFlinger 合成

在 Android 中,每一个 Surface 都拥有一个 BufferQueue

结构如下:

复制代码
Producer
Consumer

在 App 渲染流程中:

ini 复制代码
Producer  = RenderThread
Consumer  = SurfaceFlinger

RenderThread 在 GPU 渲染完成后会执行:

scss 复制代码
eglSwapBuffers()

但这里有一个容易误解的地方:

SwapBuffers 并不是传统意义的前后 buffer 交换。

真实流程是:

scss 复制代码
dequeueBuffer()
      │
GPU 渲染
      │
eglSwapBuffers()
      │
queueBuffer()

也就是:

ini 复制代码
SwapBuffers = queueBuffer

即:

把渲染完成的 buffer 提交给 BufferQueue。


SurfaceFlinger 的作用

系统中通常存在多个 Surface,例如:

sql 复制代码
Wallpaper
App Window
StatusBar
NavigationBar

SurfaceFlinger 会从每个 Surface 的 BufferQueue 中获取 buffer:

复制代码
WallpaperBuffer
AppBuffer
StatusBarBuffer

然后进行 Layer 合成

SurfaceFlinger 有两种合成方式:

1 GPU 合成(Client Composition)

arduino 复制代码
多个Surface buffer
        │
        ▼
SurfaceFlinger GPU
        │
        ▼
新的 DisplayBuffer
        │
        ▼
Display

这种情况下:

arduino 复制代码
Display 使用的是新的 buffer

2 HWC 硬件合成(Device Composition)

Android 更倾向于使用 Hardware Composer

流程:

css 复制代码
SurfaceFlinger
      │
      ▼
Hardware Composer
      │
      ▼
Display Controller

显示硬件会直接读取多个 buffer:

复制代码
Plane0 → WallpaperBuffer
Plane1 → AppBuffer
Plane2 → StatusBarBuffer

并在扫描屏幕时 硬件实时合成

HWC 的价值不是减少内存占用,而是减少 framebuffer 生成,从而降低内存带宽和 GPU 负载。


三、为什么需要 Triple Buffer

很多人第一次了解 Android 图形系统时会问:

为什么需要 3 个 buffer?双 buffer 不就够了吗?

原因在于 渲染 pipeline 的并行性

Android 渲染涉及三个阶段:

css 复制代码
App / GPU 渲染
SurfaceFlinger 合成
Display 显示

如果只有双 buffer:

css 复制代码
BufferA → Display
BufferB → GPU

当 GPU 渲染完成时,可能会遇到:

css 复制代码
Display 还没读完 BufferA

于是 GPU 只能等待:

复制代码
pipeline stall

而 Triple Buffer 可以形成流水线:

css 复制代码
BufferA → Display
BufferB → SurfaceFlinger
BufferC → GPU

时间线示意:

less 复制代码
Display : |---A---|---B---|
SF      :     |---B---|---C---|
GPU     :         |---C---|---A---|

这样三个阶段可以并行执行:

css 复制代码
GPU 渲染
SurfaceFlinger 合成
Display 扫描

从而避免卡顿。


总结

Android 渲染并不是简单的 UI 绘制,而是一条完整的 图形处理流水线

scss 复制代码
draw()
   ↓
DisplayList
   ↓
RenderThread
   ↓
GPU 渲染
   ↓
GraphicBuffer
   ↓
BufferQueue
   ↓
SurfaceFlinger
   ↓
HWC / GPU 合成
   ↓
Display Controller
   ↓
Screen

在性能分析中,这条链路的每个环节都可能成为瓶颈,例如:

  • UI 线程绘制过慢
  • GPU 渲染压力过大
  • BufferQueue 堵塞
  • SurfaceFlinger 合成压力
  • Display 同步导致的 pipeline stall

理解 Android 渲染机制,可以帮助我们更准确地定位:

复制代码
掉帧
卡顿
SwapBuffers 阻塞
GPU 瓶颈

从而进行更有效的性能优化。

声明

文中部分图片来源于网络,仅用于技术学习与交流。

如有侵权,请联系删除。

相关推荐
_Eleven1 小时前
Tiptap 完全使用指南
前端·vue.js·github
小蜜蜂dry1 小时前
nestjs学习 - 中间件(Middleware)
前端·nestjs
像我这样帅的人丶你还1 小时前
2026前端技术从「夯」到「拉」
前端
烟雨落金城1 小时前
初识Electron,谈谈感悟
前端
jeff渣渣富1 小时前
Taro 小程序构建自动化:手写插件实现图片自动上传 OSS 并智能缓存
前端·webpack
恋猫de小郭2 小时前
谷歌 Genkit Dart 正式发布:现在可以使用 Dart 和 Flutter 构建全栈 AI 应用
android·前端·flutter
vim怎么退出3 小时前
谷歌性能优化知识点总结
前端
专业抄代码选手3 小时前
在react中,TSX是如何转变成JS的
前端·javascript
葡萄城技术团队3 小时前
【实践篇】从零到一:手把手教你搭建一套企业级 SpreadJS 协同设计器
前端