显示过程

Android应用程序把经过测量、布局、绘制后的surface缓存数据,通过SurfaceFlinger把数据渲染到屏幕上,通过Android的刷新机制来刷新数据。即应用层负责绘制,系统层负责渲染,通过进程间通信把应用层需要绘制的数据传递到系统层服务,系统层服务通过显示刷新机制把数据更新到屏幕。

android的图形显示系统采用的是Client/Server架构。SurfaceFlinger(Server)由C代码编写。Client端代码分为两部分,一部分由java提供给应用层使用的API,另一部分则是由C写成的底层具体实现。

基本概念

CPU: 中央处理器,它集成了运算、缓冲、控制等单元,包括绘图功能。CPU将对象处理为多维图形,纹理(Bitmaps、Drawables等都是一起打包到统一的Texture纹理)。

GPU:一个类似于CPU的专门用来处理Graphics的处理器, 作用用来帮助加快栅格化操作,当然,也有相应的缓存数据(例如缓存已经栅格化过的bitmap等)机制。

DisplayList:它相当于是从View的绘制命令到GL命令之间的“中间语言”。它记录了绘制该View所需的全部信息,之后只要重放(replay)即可完成内容的绘制。这样如果View没有改动或只部分改动,便可重用或修改DisplayList,从而避免调用了一些上层代码,提高了效率。

栅格化:是将图片等矢量资源,转化为一格格像素点的像素图,显示到屏幕上。

FPS(Frames Per Second):表示每秒传递的帧数。通俗来讲就是指动画或视频的画面数,对应的就是APP UI界面的刷行频率,在一个UI动画的播放过程中,FPS越大,界面表现越流畅,FPS越低,界面表现越卡顿。

应用层

在android的每个view绘制中有三个核心步骤:通过Measure和Layout来确定当前需要绘制的view所在的大小和位置,通过绘制(draw)到surface,在android系统中整体绘图源码是在ViewRootImp类的performTraversals()方法,通过这个方法可以看出Measuret Layout都是递归来获取view的大小和位置,并且以深度作为优先级。由此可以看出,层级越深,元素越多,耗时也就越长。View绘制流程为:Measure-->Layout-->Draw。

Measure

用深度优先原则递归得到所有视图的宽、高;获取当前View的正确宽度childWidthMeasureSpec和高度childHeightMeasureSpec之后,可以调用它的成员函数Measure来设置它的大小。如果当前正在测量的视图是一个容器,那么它又会重复执行操作,直到它的所有子孙视图大小都测量完成为止。

Layout

用深度优先原则递归得到所有View的位置;当一个子View在应用程序窗口左上角的位置确定之后,再结合它在前面测量过程中确定的宽度和高度,就可以完全确定它在应用程序窗口中的布局

Draw

Android支持两种绘制方式:软件绘制(CPU)和硬件绘制(GPU,要求>= Android3.0)。硬件加速在UI的显示和绘制的效率远远高于CPU绘制,但硬件也有明显缺点

  • 耗电:GPU功耗比CPU高;
  • 兼容问题:某些接口和函数不支持硬件加速;
  • 内存大:使用OpenGL的接口至少需要8MB内存
系统层

Android是通过系统级进程中的SurfaceFlinger服务来把真正需要显示的数据渲染到屏幕上.
SurfaceFlinger的主要工作是:

  • 响应客户端事件,创建Layer与客户端的Surface建立连接。
  • 接收客户端数据及属性,修改Layer属性,如尺寸、颜色、透明度等。
  • 将创建的Layer内容刷新到屏幕上。
  • 维持Layer的序列,并对Layer最终输出做出裁剪计算。

在android的显示系统中使用了android的匿名共享内存:SharedClient,来实现跨进程的数据传输

  1. 每个应用和SurfaceFlinger之间都会创建一个SharedClient,一个应用对应一个SharedClient。
  2. SharedClient包含的是SharedBufferStack的集合,每个SharedClient中最多创建31个SharedBufferStack。
  3. 每个SharedBufferStack都对应一个Surface,也就是一个Window,这意味着一个android应用程序最多可以包含31个窗口。
  4. 每个SharedBufferStack中包含两个(低于4.1版本)或者三个(4.1及以上版本)缓冲区,即后面显示刷新机制中提到的双缓冲和三重缓冲技术。

最后总起来显示整体流程分三个模块:应用层绘制到缓存区;SurfaceFlinger把缓存区数据渲染到屏幕;由于是两个不同的进程,所以使用android的匿名共享内存SharedClient缓存需要显示的数据来达到目的。

绘制过程首先是CPU准备数据,通过Driver层把数据交给GPU渲染,其中CPU负责Measure、Layout、Record、Execute的数据计算工作,GPU负责栅格化、渲染。由于图形API不允许CPU直接与GPU通信,而是通过中间的一个图形驱动层(Graphics Driver)来连接两部分。图形驱动维护了一个队列,CPU把DisplayList添加到队列中,GPU从这个队列取出数据进行绘制,最终才在显示屏上显示出来。

60Hz 和 16 ms

  • 12 FPS——由于人类眼睛的特殊生理结构,如果所看画面之帧率高于每秒约10-12帧的时候,就会认为是连贯的。
  • 24 FPS——有声电影的拍摄及播放帧率均为每秒24帧,对一般人而言已算可接受。
  • 60 FPS—— 在与手机交互过程中,如触摸和反馈60帧以下人是能感觉出来的。60帧以上不能察觉变化,当帧率低于60FPS 时感觉的画面的卡顿和迟滞现象。1000ms/60=16.66ms。

刷新机制

Android系统每隔16ms发出VSync信号,触发对UI 进行渲染(即每16ms显示一帧),如果每次渲染都成功这样就能够达到流畅的画面所需要的60fps,为了能够实现60fps,这意味着计算渲染的大多数操作都必须在16ms内完成。如果某个操作花费时间是24ms,系统在得到VSync信号时就无法进行正常渲染,这样就发生了丢帧现象。那么用户在32ms内看到的会是同一幅画面,从而感觉卡顿。有很多原因可以导致CPU或者GUP负载过重从而出现丢帧现象:可能是Layout太过复杂,无法在16ms内完成渲染;可能是UI上有层叠太多的绘制单元;还有可能是动画执行次数过多。

在android4.1版本中有效处理了UI流畅性差的问题。其解决方法即在4.1版本推出的Project Buffer。Project Buffer对android Display系统进行了重构,引入三个核心元素:VSync、Triple Buffer和Choreographer。其中VSync是理解Project Buffer的核心,,简单地可以把它认为是一种定时中断技术。Choreographer起调试的作用,将绘制工作统一到VSync的某个时间点上,使应用的绘制工作有序。

  • 双缓冲:显示内容的数据内存。我们知道在Linux上通常使用Framebuffer来做显示输出,当用户进程更新Framebuffer中的数据后,显示驱动会把Framebuffer中每个像素点的值更新到屏幕,但是这样会有一个问题,如果上一帧数据还没显示完,Framebuffer中的数据又更新了,就会带来残影问题,给用户的直观感觉就会有闪烁感,所以普遍采用了双缓冲技术。双缓冲意味着要使用两个缓冲区(在SharedBufferStack中),其中一个称为Front Buffer,另一个称为Back Buffer。UI总是先在Back Buffer中绘制,然后再和Front Buffer交换,渲染到显示设备中。即只有当另一个buffer的数据准备好后,通过io_ctrl来通知显示设备切换buffer。
  • VSync(Verical Synchronization):垂直同步,从前面的双缓冲介绍中可以了解到,只有当另一个buffer准备好后,才能通知刷新 ,这就需要CPU以主动查询的方式来保证数据是否准备好,因为这种机制效率很低,所以引入了VSync。可以简单地把它认为是一种定时中断,一旦收到VSync中断,CPU就开始处理各帧数据。
  • Choreographer

卡顿的根本原因

  • 绘制任务太重,绘制一帧内容耗时太长。
  • 主线程太忙了,导致VSync信号来时还没有准备好数据导致丢帧。

耗时太长,需要从UI布局和绘制上来具体分析。这里主要讨论下第二个方面。我们知道所有的绘制工作都是由主线程,也就是UI线程来负责,主线程的关键职责是处理用户交互,在屏幕上绘制像素,并进行加载显示相关的数据。在android应用开发中 ,特别需要避免任何阻碍主线程的事情,这样应用程序才能保持对用户操作的即时响应。

主线程主要做以下几个方面工作

  • UI生命周期控制
  • 系统事件处理
  • 消息处理
  • 界面布局
  • 界面绘制
  • 界面刷新

除了这些以外,尽量避免将其他处理放到主线程中,特别是复杂的数据计算和网络请求。

打赏
支付宝 微信
上一篇 下一篇