普通的Android控件,例如TextView、Button和CheckBox等,它们都是将自己的UI绘制在宿主窗口的绘图表面之上,这意味着它们的UI是在应用程序的主线程中进行绘制的,由于应用程序的主线程除了要绘制UI之外,还需要及时地响应用户输入,否则的话,系统就会认为应用程序没有响应而出现ANR,对于一些游戏,摄像头预览、视频播放来说,它们的UI都比较复杂,而且要求能够进行高效的绘制,因此,它们的UI就不适合在应用程序的主线程中进行绘制,这时候就必须要给那些需要复杂而高效UI的视图生成一个独立的绘图表面,以及使用一个独立的线程来绘制这些视图的UI。
在Android系统中,有一种特殊的视图,称为SurfaceView,它拥有独立的绘图表面,即它不与其宿主窗口共享同一个绘图表面,由于拥有独立的绘图表面,因此SurfaceView的UI就可以在一个独立的线程中进行行绘制,又由于不占用主线程资源,SurfaceView一方面可以实现复杂而高效的UI,另一方面又不会导致用户输入得不到及时响应。
SurfaceView的绘制方式效率非常高,因为SurfaceView的窗口刷新的时候不需要重绘应用程序的窗口(android普通窗口的视图绘制机制是一层一层的,任何一个子元素或者是局部的刷新都会导致整个视图结构全部重绘一次,因此效率非常低下)。
在分析SurfaceView之前,我们先简单了解一下更底层的SurfaceFlinger和Surface:
SurfaceFlinger服务是系统服务,负责绘制Android应用程序的UI,SurfaceFlinger服务运行在Android系统的System进程中,它负责管理Android系统的帧缓冲区(Frame Buffer),Android应用程序为了能够将自己的UI绘制在系统的帧缓冲区上,它们就必须要与SurfaceFlinger服务进行通信,它们采用Binder进程间通信机制来进行通信,每一个Android应用程序与SurfaceFlinger服务都有一个连接,这个连接通过一个类型为Client的Binder对象来描述,有了这些Binder代理接口之后,Android应用程序就可以通知SurfaceFlinger服务来绘制自己的UI了,如图1所示:
应用程序在通知SurfaceFlinger服务来绘制自己的UI的时候,需要将UI元数据传递给SurfaceFlinger服务,例如,要绘制UI的区域、位置等信息,一个Android应用程序可能会有很多个窗口,而每一个窗口都有自己的UI元数据,因此,Android应用程序需要传递给SurfaceFlinger服务的UI元数据是相当可观的。在这种情况下,通过Binder进程间通信机制来在Android应用程序与SurfaceFlinger服务之间传递UI元数据是不合适的,真正使用的是Android系统的共享内存机制(Anonymous Shared Memory),在每一个Android应用程序与SurfaceFlinger服务之间的连接上加上一块用来传递UI元数据的匿名共享内存,我们就得到了图2,如下所示:
这个共享内存是通过SharedClient来描述的
一个SharedClient对应一个应用程序,在每一个SharedClient里面,有很多个SharedBufferStack共享缓冲区堆栈,是Android应用程序和SurfaceFlinger服务共享内存的地方,这个堆栈的内容是用来描述UI元数据的缓冲区,每一个SharedBufferStack在应用程序端都对应一个Surface,在SurfaceFlinger端对应一个Layer,而一个应用程序可能包含有多个Surface,这就是为什么每一个SharedClient里面包含的是一系列SharedBufferStack而不是单个SharedBufferStack。
SharedBufferStack看图5:
Surface是原始图像缓冲区SharedBufferStack的一个句柄,通过Surface就可以获取原始图像缓冲区中的GraphicBuffer,Surface可以这样理解:它是共享内存中一块区域的一个句柄,当得到一个Surface对象时,同时会得到一个Canvas(画布)对象,Canvas的方法大多数是设置画布的大小、形状、画布背景颜色等等,要想在画布上面画画,一般要与Paint对象结合使用,Paint就是画笔的风格,颜料的色彩之类的,所以得到了Surface这个句柄就可以得到其中的Canvas、还有原生缓冲器的GraphicBuffer,我们可以通过这个Canvas往GraphicBuffer填充绘制的图形数据,之后GraphicBuffer的数据会被SurfaceFlinger服务处理绘制到屏幕上。
Canvas与Surface的区别:Canvas是由Surface产生的给View绘制用的画布,ViewGroup会把自己的Canvas拆分给子View,View会在onDraw方法里将图形数据绘制在它获得的Canvas上,一个应用窗口对应一个Surface,也就是窗口最顶层的View(通常是DecorView)对应一个Surface,这个Surface是ViewRoot的一个成员变量,这个Surface在屏幕窗口建立时会被创建,SurfaceFlinger服务会负责将各个应用窗口的Surface进行合成,然后绘制到屏幕上,最终屏幕上显示的View都是通过Surface产生的Canvas把内容绘制到GraphicBuffer缓冲区中的,SurfaceFlinger把GraphicBuffer缓冲区中的内容绘制到屏幕上然后才能被我们看到。
当Android应用程序需要更新一个Surface的时候,它就会找到与它所对应的SharedBufferStack,并且从它的空闲缓冲区列表的尾部取出一个空闲的Buffer,接下来Android应用程序就请求SurfaceFlinger服务为这个Buffer分配一个图形缓冲区GraphicBuffer,分配好以后将这个图形缓冲区GraphicBuffer返回给应用程序访问,应用程序得到了图形缓冲区GraphicBuffer之后,就可以利用Surface的Canvas往里面绘制写入UI数据,写完之后,就将与GraphicBuffer所对应的缓冲区Buffer,插入到对应的SharedBufferStack的缓冲区列表的头部去,这一步完成了之后,应用程序就通知SurfaceFlinger服务去绘制GraphicBuffer的内容了。
由于SharedBufferStack是在应用程序和SurfaceFlinger服务之间共享的,应用程序关心的是它里面可以写入数据的空闲缓冲区列表,而SurfaceFlinger服务关心的是它里面的已经使用了的缓冲区列表,保存在SharedBufferStack中的已经使用了的缓冲区其实就是在排队等待渲染的数据。
以上分析了应用程序请求SurfaceFlinger服务创建Surface的过程,我们可以将Surface理解为一个绘图表面,在Android应用程序这一侧,每一个绘图表面都使用一个Surface对象来描述,Android应用程序负责往这个绘图表面填内容,在SurfaceFlinger服务这一侧,每一个窗口的绘图表面使用Layer类来描述,而SurfaceFlinger服务负责将这个绘图表面的内容取出来,并且渲染在显示屏上,有了Surface之后,Android应用程序就可以在上面绘制自己的UI了,接着再请求SurfaceFlinger服务将这个已经绘制好了UI的Surface渲染到设备显示屏上去。
有了以上知识以后,我们再来理解SurfaceView:
View通过刷新来重绘视图,Android系统通过发出VSYNC信号来进行屏幕的重绘,刷新的时间间隔一般为16ms,在一些需要频繁刷新的界面,如果刷新执行很多逻辑绘制操作,就会导致刷新使用时间超过了16ms,就会导致丢帧或者卡顿,比如你更新画面的时间过长,那么你的主UI线程会被你的绘制函数阻塞,那么将无法响应按键,触屏等消息,会造成 ANR 问题,SurfaceView虽然继承自View,但拥有独立的surface,即它不与其宿主窗口共享同一个surface,可以单独在一个线程进行绘制,并不会占用主线程的资源,这样,绘制就会比较高效,游戏,视频播放,直播,都可以用SurfaceView来实现,
SurfaceView有两个子类GLSurfaceView和VideoView。
Android系统每隔16ms发出VSYNC信号,触发GPU对UI进行渲染,如果每次渲染都成功结束,就能够达到流畅的画面所需要的60fps,这意味着程序的操作都必须在16ms内完成,
如果你的某个操作花费时间是24ms,系统在得到VSYNC信号的时候就无法进行正常渲染,用户在32ms内看到的会是同一帧画面
,这样就发生了丢帧
(卡顿现象)。
所以基于以上特点,一般分成两类
1 被动更新画面:比如棋类,这种用view就好了,因为画面的更新是依赖于 onTouch 来更新,可以直接使用 invalidate。
2 主动更新:比如一个人在一直跑动,这就需要一个单独的thread不停的重绘人的状态,避免阻塞main UI thread。
SurfaceView从API level 1时就有,继承自View,拥有View的特性,SurfaceView可以嵌入到View结构树中,因此它能够叠加在其它的视图中,它拥有一个专门用于绘制的surface,它的目的是给应用窗口提供一个额外的Surface。
每个Activity包含多个View会组成的View hierachy树形结构窗口,但是只有最顶层的根布局DecorView,才拥有一个Surface用来展示窗口内的所有内容,才是对SurfaceFlinger可见的,才在SurfaceFlinger中有一个对应的Layer,这个Surface是根布局ViewRootImpl的一个成员变量。
每个SurfaceView也有一个自己的绘图表面Surface,内部也有一个Surface成员变量,区别于它的宿主窗口的绘图表面,在SurfaceFlinger服务中也对应有一个独立的Layer,SurfaceView可以控制它的Surface的格式和尺寸,以及Surface的绘制位置。
SurfaceView的存在理由:因为View刷新时的测量(Measure),布局(Layout)以及绘制(Draw)的计算量比较大,这个流程比较耗时,像Camera的预览以及视频的播放这样的应用场景来说就不可接受了,SurfaceView的刷新不再需要以上流程,而是直接绘制在其持有的一个Surface的Canvas上,由于省去了很多步骤,其绘制性能大大提高,而SurfaceView本身只是用来控制这个Surface的大小和位置而已,并且SurfaceView允许其他线程(不是UI线程)绘制图形(使用Canvas)所以不会阻塞UI主线程,并且可以控制它的帧数,如果让这个线程1秒执行50次绘制,那么最后显示的就是50帧, 这样可以弥补View的不足,因为有时候View的帧数太低了,通过调用View的 invalidate方法通知系统重新绘制View,然后它就会调用View.onDraw方法,我们很难精确去定义View.onDraw的执行帧数。
如果一个Activity窗口的视图结构中,除了有一个DecorView顶层视图之外,还有一个SurfaceView视图,这样该Activity窗口就有两个Surface,在SurfaceFlinger服务中就对应有两个Layer,
如图1所示:
SurfaceView里面镶嵌的Surface是在包含SurfaceView的宿主Activity窗口(顶层视图对应的Surface)后面,用来描述SurfaceView的Layer的Z轴位置是小于用来描述其宿主Activity窗口的Layer的Z轴位置的,这样SurfaceView的Layer就被挡住看不见了,SurfaceView提供了一个可见区域,只有在这个可见区域内的surface部分内容才可见,就好像SurfaceView会在宿主Activity窗口上面挖一个“洞”出来,以便它的UI可以漏出来对用户可见,实际上,SurfaceView只不过是在其宿主Activity窗口上设置了一块透明区域。