https://tech.meituan.com/2017/01/19/hardware-accelerate.html
SurfaceFinger工作流程
https://juejin.cn/post/6898525503960186887
详细文章见 Android-SurfaceFlinger启动与工作原理
surfaceflinger 是在 Android 系统启动时解析 init.rc 文件启动的守护进程,在 SurfaceFlinger 的启动流程中:
- 首先会创建 SurfaceFlinger 对象,在构造器中创建了 DispSync 同步模型对象;
- 然后执行初始化 SurfaceFlinger 的逻辑:
- 注册监听,接收 HWC 的相关事件。
- 启动 APP 和 SF 的 EventThread 线程,用来管理基于 DispSync 创建的两个 DispSyncSource 延时源对象,分别是用于绘制(app--mEventThreadSource)和合成(SurfaceFlinger--mSfEventThreadSource)。启动了 EventThread 线程后,会一直阻塞在 waitForEventLocked 方法中(期间会根据需要设置监听器),直到接收到 Vsync 信号且至少有一个连接正在等待 Vsync 信号才会继续执行线程逻辑,即通知监听者;
- 通过 MessageQueue.setEventThread 方法创建了一个连接,并通过 Looper.addFd 方法监听 BitTube 数据。
- 创建 HWComposer 对象(通过 HAL 层的 HWComposer 硬件模块 或 软件模拟产生 Vsync 信号),现在的 Android 系统基本上都可以看成是通过硬件 HWComposer 产生 Vsync 信号,而不使用软件模拟,所以下面解析都只谈及硬件 HWComposer 的 Vsync 信号;
- 初始化非虚拟的显示屏;
- 启动开机动画服务;
- 最后执行 SurfaceFlinger.run 逻辑,该方法会在 SurfaceFlinger 主线程通过死循环执行 MessageQueue.waitMessage 方法等待消息的到来,其内部调用了 Looper.pollOnce 方法,该方法会从 Looper.addFd 方法监听的 BitTube 中读取数据,当有数据到来时执行对应的回调方法。
当硬件或软件模拟发出 Vsync 信号时:
- 回调 SF 相关方法,SF 调用 DispSync 同步模型的方法处理 Vsync 信号(统计和计算模型的偏移和周期),并根据返回值判断是否使能/关闭 HWC Vsync 信号的发出。
- DispSync 根据计算的偏移和周期计算下次 Vsync 信号发生时间,并通知监听者 Vsync 信号到达的事件,传递给 DispSyncSource 延时源,延时源通过 EventThread 来管理 Vsync 信号的收发。
- EventThread 调用连接 Connection 对象向 BitTube 发送数据,触发 addFd 函数中设置的回调方法,回调方法进而调用 SF.onMessageReceived 函数,然后进行图像的合成等工作。
另一方面,Choreographer 会通过上面创建的 APP 延时源 mEventThreadSource 对象及其对应的 EventThread 线程来监听同步模拟发出的 Vsync 信号,然后进行绘制(measure/layout/draw)操作。具体逻辑见 Android-Choreographer工作原理。
将 SurfaceFlinger 的工作流程总结如下图:
详细文章见 Android-Choreographer工作原理
- Choreographer: 使 CPU/GPU 的绘制是在 VSYNC 到来时开始。Choreographer 初始化时会创建一个表示对 Vsync 信号感兴趣的连接,当有绘制请求时通过 postCallback 方法请求下一次 Vsync 信号,当信号到来后才开始执行绘制任务。
- 只有当 App 注册监听下一个 Vsync 信号后才能接收到 Vsync 到来的回调。如果界面一直保持不变,那么 App 不会去接收每隔 16.6ms 一次的 Vsync 事件,但底层依旧会以这个频率来切换每一帧的画面(也是通过监听 Vsync 信号实现)。即当界面不变时屏幕也会固定每 16.6ms 刷新,但 CPU/GPU 不走绘制流程。
- 当 View 请求刷新时,这个任务并不会马上开始,而是需要等到下一个 Vsync 信号到来时才开始;measure/layout/draw 流程运行完后,界面也不会立刻刷新,而会等到下一个 VSync 信号到来时才进行缓存交换和显示。
- 造成丢帧主要有两个原因:一是遍历绘制 View 树以及计算屏幕数据超过了16.6ms;二是主线程一直在处理其他耗时消息,导致绘制任务迟迟不能开始(同步屏障不能完全解决这个问题)。
- 可通过Choreographer.getInstance().postFrameCallback()来监听帧率情况。
阅读这篇文章建议先阅读 Android-SurfaceFlinger启动与工作原理 这篇文章,然后结合 Choreographer 的工作流程,可以对 Vsync 信号是怎么协调 App 端的绘制任务以及 SurfaceFlinger 的合成任务有一个比较清晰的认识。
用一张图总结一下 Choreographer 的工作流程:
详细文章见 Android-Window机制源码解读 和 Android-View绘制原理
在 Choreographer 接收到 Vsync 信号后便开始 View 的绘制过程,即老生常谈的 measure, layout, draw 三大步骤。
Surface工作流程及软硬件绘制详细文章见 Android-Surface之创建流程及软硬件绘制 和 Android-Surface之双缓冲及SurfaceView解析
在 View 的 draw 过程中会接触到 Surface 的逻辑,通过它可以向 SurfaceFlinger 申请一块缓存区用来绘制。绘制任务可以分为软件绘制与硬件绘制两种。
- Java 层的 Surface 对象中 mNativeObject 属性指向 native 层中创建的 Surface 对象。
- Surface 对应 SurfaceFlinger 中的 Layer 对象,它持有 Layer 中的 BufferQueueProducer 指针(生产者),通过这个生产者对象可以在绘制时向 BufferQueue 申请一块空闲的图形缓存区 GraphicBuffer,在 Surface 上绘制的内容会存入该缓存区内。
- SurfaceFlinger 通过 BufferQueueConsumer 消费者从 BufferQueue 中取出 GraphicBuffer 中的数据进行合成渲染并送到显示器显示。
软件绘制
软件绘制可能会绘制到不需要重绘的视图,且其绘制过程在主线程进行的,可能会造成卡顿等情况。它把要绘制的内容写进一个 Bitmap 位图,其实就是填充到了 Surface 申请的图形缓存区里。
软件绘制可分为三个步骤:
- Surface.lockCanvas -- dequeueBuffer 从 BufferQueue 中出队列一块缓存区。
- View.draw -- 绘制内容。
- Surface.unlockCanvasAndPost -- queueBuffer 将填充了数据的缓存区存入 BufferQueue 队列中,然后通知给 SurfaceFlinger 进行合成(请求 Vsync 信号)。
硬件绘制
硬件绘制会将绘制函数作为绘制指令(DrawOp)记录在一个列表(DisplayList)中,然后交给单独的 Render 线程使用 GPU 进行硬件加速渲染。它只需要针对需要更新的 View 对象的脏区进行记录或更新,无需更新的 View 对象则能重用先前 DisplayList 中记录的指令。
硬件绘制可分为两个阶段:
- 构建阶段:将 View 的绘制操作(drawLine...)抽象成 DrawOp 操作并存入 DisplayList 中。
- 绘制阶段:首先分配缓存区(同软件绘制),然后将 Surface 绑定到 Render 线程,最后通过 GPU 渲染 DrawOp 数据。
硬件加速的内存申请跟软件绘制一样都是借助 Layer 中的 BufferQueueProducer 生产者从 BufferQueue 中出队列一块空闲缓存区 GraphicBuffer 用来渲染数据的,之后也都会通知 SurfaceFlinger 进行合成。不一样的地方在于硬件加速相比软件绘制而言算法可能更加合理,同时采用了一个单独的 Render 线程,减轻了主线程的负担。
双缓冲
一般来说将双缓冲用到的两块缓冲区称为 -- 前缓冲区(front buffer) 和 后缓冲区(back buffer)。显示器显示的数据来源于 front buffer 前缓存区,而每一帧的数据都绘制到 back buffer 后缓存区,在 Vsync 信号到来后会交互缓存区的数据(指针指向),这时 front buffer 和 back buffer 的称呼及功能倒转。
在 View 的绘制过程中 Surface 使用了双缓冲技术。
SurfaceView
SurfaceView 就是一块拥有自己独立 Surface 的特殊 View 视图,由于这个特性,它一般用来实现比较复杂的图像或动画/视频的显示。
用一张图总结一下 Android 软硬件绘制的流程:
到这里总算将 Android 图形系统的工作流程串联起来了,在阅读了源码之后感觉对这个流程有了更清晰的理解,而不是跟着网上的结论人云亦云,感觉每个人说的都有道理,但是总有些地方觉得又有冲突,自己阅读源码跟看别人阅读源码之后给出的结论,感觉确实不一样,Read The Fucking Source Code
是一种很好的解惑手段,接下来剩下的就是这些流程中的某些细节了,比如说 requestLayout 和 invalidate 方法的区别之类的,这些相关的问题以后有时间也会慢慢记录一下。文中内容如有错误欢迎指出,共同进步!觉得不错的留个赞再走哈~
用一张图总结一下图形系统工作的整体流程: