60fps背后的秘密:Android图层数据与控制的分离哲学
这就是主要角色类的实例化的顺序过程。分析:Surface的创建过程中,不得不先分析SurfaceControl和Layer的创建,下图是SurfaceControl的创建过程的简化版(详细的代码细节非常多,不便于大家记忆理解,而且太细节容易让大家脑子更糊涂),其中涉及Binder跨进程调用以及JNI调用等。这个在网上很多博客写的还是上面的流程,包括在23、24年的博客,而且用Deepseek等AI
点击上方“IT烂笔头”,选择“置顶公众号”
第一时间获取 IT 技术干货!

阅读文本大概需要 15 分钟。
上一文中主要分析了整个Android图形子系统的整体运作流程,本节我们将探索运作流程中的一些关键类创建的时机,从而更好的理解这个过程,以及分析其中的设计逻辑。

























①简单类的创建时机
- DecorView
-
-
- DecorView在
Activity.setContentView()调用时由PhoneWindow同步创建,作为承载应用主题框架和用户布局的顶级容器。
- DecorView在
-

-
- 此时 DecorView 尚未显示
:其可见性需等待
Activity.onResume()后通过WindowManager.addView()与ViewRootImpl绑定完成绘制流程。 - 主题决定结构:
DecorView的布局因主题而异(如全屏/带标题栏),由
generateLayout()动态选择系统资源。
- 此时 DecorView 尚未显示
- PhoneWindow
-
- Activity

- Activity
PhoneWindow 在 Activity.attach() 方法中同步创建,作为 Activity 的窗口代理,负责管理窗口样式、事件回调和视图层级。
-
- Dialog

- Dialog
【过期的版本】Android 6.0之前,Dialog通过 PolicyManager.makeNewWindow() 创建 PhoneWindow 对象,具备完整的 DecorView和事件回调能力。
这个在网上很多博客写的还是上面的流程,包括在23、24年的博客,而且用Deepseek等AI去查,结果都是上面的流程,可见AI不是去查最近的源码库,而是基于网上的博客做整理而已。我们去读源码发现,并不是这样,PolicyManager.java早已经被移除了,这个设计因为很少需要扩展被认为是过度抽象设计而增加维护成本,所以从Android 6.0之后,PhoneWindow都是在Dialog和Activity之中直接new来创建,如下:


























Dialog 在构造函数中直接创建 PhoneWindow 实例,并在 show() 阶段通过关联 Activity 的 Token 完成窗口注册。
-
- Toast

- Toast
Toast不创建PhoneWindow,仅通过ViewGroup(如 LinearLayout)作为根视图,因此无法直接接收输入事件。
关键步骤:
-
-
- 通过
NMS排队 Toast 请求; TN收到回调后,在主线程调用WindowManager.addView()添加视图;- 自动触发延时消息,定时移除视图
- 通过
-
- ViewRootImpl
-
ViewRootImpl在Activity.onResume()后通过WindowManager.addView()创建,作为连接DecorView与WindowManagerService的核心枢纽,负责驱动 UI 绘制、事件分发及窗口管理。

























②Surface相关类
- Surface
-
-
- 概括:Surface 的创建由窗口状态变更(如首次显示、尺寸调整、层级更新)触发,通过
ViewRootImpl → WMS → SurfaceFlinger的跨进程协作完成 Layer 注册,最终以句柄形式关联至应用层。
- 概括:Surface 的创建由窗口状态变更(如首次显示、尺寸调整、层级更新)触发,通过
-
-
-
- 分析:Surface的创建过程中,不得不先分析SurfaceControl和Layer的创建,下图是SurfaceControl的创建过程的简化版(详细的代码细节非常多,不便于大家记忆理解,而且太细节容易让大家脑子更糊涂),其中涉及Binder跨进程调用以及JNI调用等。简单描述下就是wms通过中间一些类的转发最终会调用SurfaceFlinger创建一个Layer(Layer就是在SurfaceFlinger当中对图层的抽象,应用层说的Surface可以理解为在SF这里的Layer)。然后在创建SurfaceControl的时候将Layer关联进去。
-
-

*outSurface = sp<SurfaceControl>::make(this, result.handle, result.layerId, toString(result.layerName), w, h, format, result.transformHint, flags);
那我们来看看SurfaceControl与Layer是怎么关联的呢?在最终创建SurfaceControl的时候,我们看到构造函数里有result.handle,result.layerId等等,看命名是不是和Layer有什么关系呢?不错确实关系很大,result是CreateSurfaceResult的实例,在Layer创建的时候会将LayerHandle交给CreateSurfaceResult的handle,然后在SurfaceControl构造函数中传入CreateSurfaceResult的handle也就是LayerHandle,那么这个LayerHandle是什么呢?
class LayerHandle : public BBinder {public: LayerHandle(const sp<android::SurfaceFlinger>& flinger, const sp<android::Layer>& layer); static sp<android::Layer> getLayer(const sp<IBinder>& handle); ... }
不用关心上面的代码细节,只需要记住LayerHandle就是一个BBinder。里面有一个接口getLayer可以获取Layer,也就是说拿到这个handle就可以去操作Layer了,那么在SurfaceControl的构造函数里面传入了上面的handle,SurfaceControl间接的持有Layer了:
























到这里我们大致了解了SurfaceControl和Layer的创建过程以及直接的关联过程,那么C++层的Surface到底是怎么创建的还没提到。上面的SurfaceControl创建走完,回到WMS类中:

再往上面一直回到ViewRootImpl中,


为了方便理解,以时序图展示C++层Surface创建调用过程:






















通过代码和上图可知,最后是通过BLASTBufferQueue去创建Surface,那么BLASTBufferQueue是什么?BLASTBufferQueue代替之前的BufferQueue负责整个缓冲帧的生产者-消费者模型的实现(实现在客户端(new BlastBufferQueue),不在SurfaceFlinger当中,减少IPC),通过BLASTBufferQueue创建的Surface里面会持有这个模型的Producer(生产者),所以Surface可以对缓冲帧(GraphicBuffer)进行申请用来绘制以及绘制完成后入到BLASTBufferQueue中。




















Surface创建的时候也会通过SurfaceControl->handle传参到Surface当中,也就是Surface也会持有Layer的句柄,这个是用来传递图层属性(位置,透明度等等),而持有Producer是用来处理图层数据的,为什么要分离?
BLASTBufferQueue传递GraphicBuffer是通过共享内存的方式,可以快速的访问帧的画面数据,

优化设计:
-
-
- 零拷贝(Zero-Copy)
数据始终位于共享内存中,生产者与消费者通过 同一块物理内存 读写,避免数据复制。 - 内存映射同步
通过lock()/unlock()控制 CPU 访问权限,并触发 Cache 同步(如 GPU 渲染后需同步 Cache 到内存)。 - 硬件加速支持
Gralloc 根据usage标志(如USAGE_HW_TEXTURE)优化内存布局,确保 GPU 可直接访问。
- 零拷贝(Zero-Copy)
-
与传统共享内存的差异:

通过Layer的句柄传递的位置和透明度属性是需要IPC,也是通过事务的方式减少Binder调用的次数。
举个例子说明下图层数据与图层属性分开传输:
假设开发一个绘图 App,用户手指滑动时在悬浮窗上绘制红色贝塞尔曲线,同时窗口可拖动(位置变化)。
-
-
- 图形数据:手指轨迹生成的红色曲线像素(通过
Path绘制到GraphicBuffer) - 控制指令:悬浮窗的位置、透明度、层级(通过
SurfaceControl.Transaction设置)
- 图形数据:手指轨迹生成的红色曲线像素(通过
-
实现步骤:
-
-
- 图形数据生成
-
当用户手指移动时,系统通过 Path.quadTo() 生成贝塞尔曲线路径,最终渲染到 GraphicBuffer:

-
-
- 控制指令提交
-
当用户拖动悬浮窗时,修改窗口位置(不改变曲线内容)

两者协同与分离的意义:

为什么必须分离?
-
-
- 解耦渲染与合成
-
-
-
-
- 应用只需关注绘制内容(如曲线生成),系统服务独立管理合成策略(如叠加顺序、硬件加速)。
-
-
-
-
- 降低 IPC 开销
-
-
-
-
- 高频的绘制(60+ fps)与低频的属性变更(如窗口移动)分离,避免每次绘制都触发 IPC。
-
-
-
-
- 动态响应性
-
-
-
-
- 控制指令可随时修改(如窗口即时拖动),而图形数据按帧生成(需等待渲染完成)。
-
-
此设计是 Android 高帧率流畅交互的基础:图形数据保障内容正确性,控制指令保障交互响应性,两者通过 VSync 信号同步协作。

























③小结
通过上述分析及图像子系统的工作流程可知:首先实例化的是 PhoneWindow 对象。随后,当调用 setContentView 方法时,PhoneWindow 会实例化 DecorView。接着,在 ActivityThread 处理 Activity 的 resume 过程中实例化 ViewRootImpl,此时正式启动画面显示流程。ViewRootImpl 会发起请求,通过窗口管理系统 (WMS) 调用 SurfaceFlinger,由后者实例化 Layer 及 SurfaceControl。最终,ViewRootImpl 会通过 JNI 在 Native 层创建 Surface,该 Surface 后续将用于申请 GraphicBuffer 来存储渲染数据。这就是主要角色类的实例化的顺序过程。
火山引擎开发者社区是火山引擎打造的AI技术生态平台,聚焦Agent与大模型开发,提供豆包系列模型(图像/视频/视觉)、智能分析与会话工具,并配套评测集、动手实验室及行业案例库。社区通过技术沙龙、挑战赛等活动促进开发者成长,新用户可领50万Tokens权益,助力构建智能应用。
更多推荐
所有评论(0)