Android图像系统与Choreographer

一.图像显示系统概述

一个基础的图像显示系统由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。 当出现一次掉帧后,会启用三缓存机制,以此减少后续掉帧问题的发生。 当然,帧缓存也不是越多越好,帧缓存过多会造成内存压力,降低画面的实时性。

二.Choreographer

Choreographer是Android中用于监听VSync信号,并在VSync信号到来时执行指定任务的组件。Choreographer是线程单实例,它的创建依赖当前线程的Looper,可以通过Choreographer的静态方法getInstance获取。

1.Choreographer的初始化

在Choreographer的构造方法中,会创建三个重要的对象mHandler、mDisplayEventReceiver、mCallbackQueues,它们对应的类型分别为FrameHandler、FrameDisplayEventReceiver、Array。

  • FrameHandler:用于将操作切换到Choreographer对应的线程。
  • FrameDisplayEventReceiver:用于请求和接收VSync信号。
  • Array<CallbackQueue>:用于添加待执行的任务,由于callbackQueue的类型有五种类型,因此数组长度为5。
java 复制代码
    //输入事件,首先执行
    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方法。

相关推荐
2601_951643772 小时前
Python第一,Java跌出前三,C语言杀回来了
java·c语言·python·编程语言排行·技术趋势
IT 行者4 小时前
GitHub Spec Kit 实战(五):/speckit.tasks 怎么拆——Spec Kit 五部曲收官
java·ai编程·claude
(Charon)4 小时前
【C++ 面试高频基础:指针、引用、const、static、new/delete 总结】
java·开发语言
Yeats_Liao5 小时前
Feed流系统设计(三):数据模型与存储设计,从表结构到Redis收件箱
java·javascript·redis
JiaHao汤5 小时前
分布式事务方案全景:从理论到 Seata 落地
java·分布式·spring·spring cloud
色空大师6 小时前
【debug调试详解-idea】
java·ide·intellij-idea·调试·远程调试
程序猿阿越6 小时前
AutoMQ源码(一)读、写、Compaction
java·后端·源码
ywl4708120876 小时前
jwt生产token,简单版helloworld
java·数据库·spring
未若君雅裁6 小时前
生产问题排查与性能瓶颈定位:日志、监控、链路追踪、压测与Arthas
java·web安全
器灵科技6 小时前
AI视频工具实测:Seedance/可灵/HappyHorse谁最能打?
java·运维·数据库·人工智能·github