一.图像显示系统概述
一个基础的图像显示系统由CPU、GPU、屏幕三部分组成。CPU负责计算帧数据,GPU负责渲染图形数据,屏幕负责图像的显示。在屏幕显示图像时,会按照从上向下逐行扫描的方式扫描每一帧图像的像素。
由于CPU与GPU处理数据的时间不可控,因此会导致屏幕刷新率与渲染帧率不一致。具体表现为在扫描第一帧图像扫描的过程中出现第二帧图像,引起屏幕中画面的撕裂。
1.双缓存机制和VSync机制
为了解决画面的撕裂,在图像显示系统中引入了双缓存机制和VSync机制。
双缓存机制是指使用frameBuffer和backBuffer两块内存作为帧缓存,屏幕每次从frameBuffer中获取当前帧图像进行扫描,在屏幕扫描的同时,CPU与GPU使用backBuffer渲染下一帧图像。
VSync机制是指当屏幕扫描完最后一行像素时,需要返回到第一行像素的位置,这时屏幕设备会产生一个垂直同步信号,即VSync。在收到VSync信号后,frameBuffer与backBuffer中的图像数据进行交换。交换完成后,屏幕会继续从frameBuffer中扫描下一帧图像。屏幕每秒钟产生的VSync信号的次数就是帧率。
当一个VSync信号发出后,如果在下一次VSync信号到来前,GPU还没有完成下一帧数据的渲染,此时frameBuffer中的数据无法与backBuffer中的数据进行交换,会出现屏幕持续扫描同一帧图像的现象,即掉帧。
2.三缓存机制
通常情况下,掉帧是由于绘制的图像过于复杂,需要消耗大量的时间。为了解决这个问题,Android系统会在每次收到VSync信号后,尽可能去安排CPU计算帧数据,以此来减少掉帧问题的发生。
同时,Android引入了三缓存机制。三缓存机制就是在frameBuffer和backBuffer之外,又加入了graphicBuffer。 当出现一次掉帧后,会启用三缓存机制,以此减少后续掉帧问题的发生。 ![UML 图_edit_104867516109518.jpg](https://img-blog.csdnimg.cn/img_convert/1cb996ddc43cb4b896b5b2add81bcbf2.webp?x-oss-process=image/format,png) 当然,帧缓存也不是越多越好,帧缓存过多会造成内存压力,降低画面的实时性。
二.Choreographer
Choreographer是Android中用于监听VSync信号,并在VSync信号到来时执行指定任务的组件。Choreographer是线程单实例,它的创建依赖当前线程的Looper,可以通过Choreographer的静态方法getInstance获取。
1.Choreographer的初始化
在Choreographer的构造方法中,会创建三个重要的对象mHandler、mDisplayEventReceiver、mCallbackQueues,它们对应的类型分别为FrameHandler、FrameDisplayEventReceiver、Array。
- FrameHandler:用于将操作切换到Choreographer对应的线程。
- FrameDisplayEventReceiver:用于请求和接收VSync信号。
- Array:用于添加待执行的任务,由于callbackQueue的类型有五种类型,因此数组长度为5。
//输入事件,首先执行
public static final int CALLBACK_INPUT = 0;
//动画,第二执行
public static final int CALLBACK_ANIMATION = 1;
//插入更新的动画,第三执行
public static final int CALLBACK_INSETS_ANIMATION = 2;
//绘制,第四执行
public static final int CALLBACK_TRAVERSAL = 3;
//提交,最后执行,
public static final int CALLBACK_COMMIT = 4;
2.Choreographer的使用
2.1 添加任务
当需要在VSync信号到来时执行某些操作,可以通过调用Choreographer的postCallback方法实现,该方法接收一个Runnable对象作为参数。
在Choreographer的postCallback方法中会调用postCallbackDelayed方法,最终会调用postCallbackDelayedInternal方法。
在Choreographer的postCallbackDelayedInternal方法内部主要做了四件事:
1)计算添加当前任务的时间戳。
2)根据任务类型,获取任务队列。
3)将任务添加到任务队列中。
4)请求VSync信号。
在CallbackQueue的addCallbackLocked方法中,主要做了三件事:
1)将当前任务封装成CallbackRecord。
2)从头遍历任务链表,按照添加任务的时间戳从小到大的顺序,找到当前任务对应的位置。
3)将当前任务插入到对应位置。
在Choreographer的scheduleFrameLocked方法中,主要做了三件事:
1)检查当前方法是否运行在Choreographer对应的线程。
2)如果运行在Choreographer对应的线程,则请求VSync信号。
3)如果没有运行在Choreographer对应的线程,则通过FrameHandler切换到对应线程请求VSync信号。
2.1 任务执行
当VSync信号到来时,会回调FrameDisplayEventReceiver的onVsync方法。onVsync方法内部会调用FrameHandler方法发送异步消息,将线程切换到Choreographer对应的线程。
最终会调用FrameDisplayEventReceiver的run方法。由于FrameDisplayEventReceiver是Choreographer的内部类,run方法内部会直接调用Choreographer的doFrame方法。在doFrame方法中,首先会根据时间戳计算并处理掉帧,然后按照CALLBACK_INPUT、CALLBACK_ANIMATION、CALLBACK_INSETS_ANIMATION、CALLBACK_TRAVERSAL、CALLBACK_COMMIT顺序,调用doCallbacks方法。
在Choreographer的doCallbacks方法中,主要做了三件事:
1)根据任务类型,获取任务队列。
2)从任务队列中获取第一个任务并执行。
3)获取下一个任务,继续执行,直到任务队列中所有任务都执行完毕。
在CallbackRecord的run方法中,会根据添加任务时传递的token区分任务类型。
如果是通过postVsyncCallback方法添加的任务,则任务类型为VsyncCallback,会调用VsyncCallback的onVsync方法。
如果是通过postFrameCallback方法添加的任务,则任务类型为FrameCallback,会调用FrameCallback的doFrame方法。
如果是通过postCallback方法添加的任务,则任务类型为Runnable,会调用Runnable的run方法。
如果你看到了这里,觉得文章写得不错就给个赞呗?
更多Android进阶指南 可以扫码 解锁更多Android进阶资料
敲代码不易,关注一下吧。ღ( ´・ᴗ・` )