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方法。

相关推荐
虾条_花吹雪几秒前
Chat Model API
java
双力臂4046 分钟前
MyBatis动态SQL进阶:复杂查询与性能优化实战
java·sql·性能优化·mybatis
六毛的毛37 分钟前
Springboot开发常见注解一览
java·spring boot·后端
程序漫游人1 小时前
centos8.5安装jdk21详细安装教程
java·linux
超级码.里奥.农1 小时前
零基础 “入坑” Java--- 七、数组(二)
java·开发语言
hqxstudying2 小时前
Java创建型模式---单例模式
java·数据结构·设计模式·代码规范
挺菜的2 小时前
【算法刷题记录(简单题)002】字符串字符匹配(java代码实现)
java·开发语言·算法
A__tao2 小时前
一键将 SQL 转为 Java 实体类,全面支持 MySQL / PostgreSQL / Oracle!
java·sql·mysql
一只叫煤球的猫2 小时前
真实事故复盘:Redis分布式锁居然失效了?公司十年老程序员踩的坑
java·redis·后端
猴哥源码2 小时前
基于Java+SpringBoot的农事管理系统
java·spring boot