android SurfaceView 详解

普通的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应用程序的UISurfaceFlinger服务运行在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就可以获取原始图像缓冲区中的GraphicBufferSurface可以这样理解:它是共享内存中一块区域的一个句柄当得到一个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服务将这个已经绘制好了UISurface渲染到设备显示屏上去

有了以上知识以后,我们再来理解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窗口上设置了一块透明区域。

如上Activity窗口的顶层视图DecorView及其两个TextView控件都是通过窗口的Surface对应的Canvas绘制在SurfaceFlinger服务中的同一个Layer上,而SurfaceView的UI是通过其自己的Surface对应的Canvas绘制在SurfaceFlinger服务中的另外一个Layer上。



虽然布局中的SurfaceView在View hierachy树结构中,但它的Surface与宿主窗口是分离的,因此这个Surface不在View hierachy中,它的显示也不受View的属性控制,所以和普通的View不同的地方是不能执行Transition,Rotation,Scale等转换,不能进行Alpha透明度运算,一些View中的特性也无法使用 

要了解 SurfaceView ,还须了解它的另外两个组件:Surface 和 SurfaceHolder,他们三者之间的关系实质上就是 MVC,Model就是数据模型的意思也就是这里的Surface;View即视图也就是这里的SurfaceView;SurfaceHolder很明显可以理解为Controller(控制器)。

SurfaceView中 你可以通过SurfaceHolder接口访问它内部的surface, 而我们执行绘制的方法就是操作这个 Surface内部的Canvas处理Canvas画的效果和动画,大小,像素等,getHolder()方法可以得到这个SurfaceHolder通过SurfaceHolder来控制surface的尺寸和格式,或者修改监视surface的变化等等。

SurfaceHolder 有三个回调方法可以监听SurfaceView中的surface的生命周期,SurfaceView一开始创建出来后,它拥有的Surface不一定会一起创建出来, SurfaceView 变得可见时,surface被创建, SurfaceView 隐藏前,surface被销毁, 被创建了表示可以开始准备绘制了,而被销毁后我们要释放其他资源 Surfaceview一般会继承SurfaceHolder的Callback接口, SurfaceHolder.Callback具有如下的方法:
    surfaceCreated(SurfaceHolder holder) :当Surface第一次创建后会立即调用该函数,可以在该函数中做些和绘制界面相关的初始化工作,一般情况下都是在新线程来绘制界面,所以不要在这个函数中绘制Surface。 
    surfaceChanged(SurfaceHolder holder, int format, int width,int height) :当Surface的状态(大小和格式)发生变化的时候会调用该函数,在surfaceCreated调用后该函数至少会被调用一次。 
    surfaceDestroyed(SurfaceHolder holder) :当Surface被摧毁前会调用该函数,该函数被调用后就不能继续使用Surface了,一般在该函数中来清理使用的资源。 

特别需要注意的是SurfaceView和SurfaceHolder.Callback的所有回调方法都是在主线程中回调的,在绘制前必须先合法的获取 Surface 才能开始绘制内容, SurfaceHolder.Callback.surfaceCreated() 和 SurfaceHolder.Callback.surfaceDestroyed() 之间的状态为合法的,在这之外使用Surface都会出错。

在使用SurfaceView过程中是不直接和Surface打交道的,由SurfaceHolder的Canvas lockCanvas()或则Canvas lockCanvas(Rect dirty)函数来锁定并且获取Surface中的Canvas画布对象,通过在Canvas上绘制内容来修改Surface中的数据,如果Surface被别的线程占有不可编辑或则尚未创建或者已经被销毁,调用该函数会返回null。

unlockCanvas() 和 lockCanvas()之间Surface的内容是不缓存的,所以需要完全重绘Surface的内容,如果为了提高效率只重绘变化的部分则可以调用lockCanvas(Rect dirty)函数来指定一个dirty区域,这样该区域外的内容会缓存起来,只更新需要重绘的区域, 相对部分内存要求比较高的游戏来说,不重画dirty外的其他区域的像素,可以提高速度。

在调用lockCanvas函数获取Surface的Canvas后,SurfaceView会利用Surface的一个同步锁锁住画布 Canvas, 直到调用unlockCanvasAndPost(Canvas canvas)函数,才 解锁画布并提交改变,将图形显示 ,这里的同步机制保证Surface的Canvas在绘制过程中不会被改变(被摧毁、修改), 避免多个不同的线程同时操作同一个Canvas对象

双缓冲:SurfaceView在更新视图时用了两个Canvas,一张frontCanvas和一张backCanvas,每次实际显示的是frontCanvas,backCanvas存储的是上一次更改前的视图,当使用lockCanvas()获取画布时,得到的实际上是backCanvas而不是正在显示的frontCanvas,当你在获取到的backCanvas上绘制完成后,再使用unlockCanvasAndPost(canvas)提交 backCanvas 视图,那么这张 backCanvas 将替换正在显示的frontCanvas被显示出来,原来的frontCanvas将切换到后台作为backCanvas,这样做的好处是在绘制期间不会出现黑屏。

SurfaceView类的成员变量mRequestedType描述的是SurfaceView的绘图表面Surface的类型,一般来说,它的值可能等于SURFACE_TYPE_NORMAL或者SURFACE_TYPE_PUSH_BUFFERS,
当一个SurfaceView的绘图表面的类型等于SURFACE_TYPE_NORMAL的时候,就表示该SurfaceView的绘图表面所使用的内存是一块普通的内存,一般来说,这块内存是由SurfaceFlinger服务来分配的,我们可以在应用程序内部自由地访问它,即可以在它上面填充任意的UI数据,然后交给SurfaceFlinger服务来合成,并且显示在屏幕上,在这种情况下,在SurfaceFlinger服务一端使用一个Layer对象来描述该SurfaceView的绘图表面。
当一个SurfaceView的绘图表面的类型等于SURFACE_TYPE_PUSH_BUFFERS的时候,就表示该SurfaceView的绘图表面所使用的内存不是由SurfaceFlinger服务分配的,我们不能够在应用程序内部对它进行操作,所以不能调用lockCanvas来获取Canvas对象进行绘制了,例如当一个SurfaceView是用来显示摄像头预览或者视频播放的时候,我们就会将它的绘图表面的类型设置为SURFACE_TYPE_PUSH_BUFFERS,这样摄像头服务或者视频播放服务就会为该SurfaceView绘图表面创建一块内存,并且将采集的预览图像数据或者视频帧数据源源不断地填充到该内存中去,在这种情况下,在SurfaceFlinger服务一端使用一个LayerBuffer对象来描述该SurfaceView的绘图表面。
所以:决定surfaceView的内存是普通内存(由开发者自己决定用来绘制什么)还是专用的内存(显示摄像头或者视频等,开发者无法使用这块内存)由mRequestType决定,我们在创建了一个SurfaceView之后,可以调用它的SurfaceHolder对象的成员函数setType来修改该SurfaceView的绘图表面的类型,绘图表面类型为SURFACE_TYPE_PUSH_BUFFERS的SurfaceView的UI是不能由应用程序来控制的,而是由专门的服务来控制的,例如,摄像头服务或者视频播放服务。
SurfaceView类的成员变量mRequestedType目前接收如下的参数:
SURFACE_TYPE_NORMAL:用RAM缓存原生数据的普通Surface 
SURFACE_TYPE_HARDWARE:适用于DMA(Direct memory access )引擎和硬件加速的Surface 
SURFACE_TYPE_GPU:适用于GPU加速的Surface 
SURFACE_TYPE_PUSH_BUFFERS:表明该Surface不包含原生数据,Surface用到的数据由其他对象提供。 

从总体上描述了SurfaceView的大致实现原理之后,接下来我们就详细分析它的具体实现过程,包括它的:
1、绘图表面的创建过程,2、在宿主窗口上面进行挖洞的过程,3、绘制过程。

 1. SurfaceView的绘图表面的创建过程

由于SurfaceView具有独立的绘图表面,因此,在它的UI内容可以绘制之前,我们首先要将它的绘图表面创建出来,尽管SurfaceView不与它的宿主窗口共享同一个绘图表面,但是它仍然是属于宿主窗口的视图树的一个结点,也就是说,SurfaceView仍然是会参与到宿主窗口的某些执行流程中去。

每当一个窗口需要刷新UI时,就会调用ViewRoot类的成员函数performTraversals,该函数在执行的过程中,如果发现应用窗口的成员变量Surface还没有创建或者已经失效了,那么就会请求WindowManagerService服务创建一个新的绘图表面作为本窗口的绘图表面,同时,如果布局里面使用了SurfaceView,它还会让嵌入在窗口里面的SurfaceView通过调用SurfaceView.updateWindow()方法创建它对应的Surface


 2. SurfaceView的挖洞过程

为了保证SurfaceView的UI是可见的,SurfaceView需要在其宿主窗口的绘图表面上设置一块透明区域,以便可以将自己显示出来,SurfaceView在被附加到宿主窗口之上的时候,会请求在宿主窗口上设置透明区域,而每当其宿主窗口刷新自己的UI的时候,就会将所有嵌入在它里面的SurfaceView所设置的透明区域收集起来,然后再通知WindowManagerService服务为其设置一个总的透明区域。


3. SurfaceView的绘制过程

先看看Surface类的实现

  1. public class Surface implements Parcelable {  
  2.     ......  
  3.   
  4.     private int mSurfaceControl;  
  5.     ......  
  6.     private Canvas mCanvas;  
  7.     ......  
  8.     private String mName;  
  9.     ......  
  10.   
  11.     public Surface(SurfaceSession s,  
  12.             int pid, String name, int display, int w, int h, int format, int flags)  
  13.         throws OutOfResourcesException {  
  14.         ......  
  15.   
  16.         mCanvas = new CompatibleCanvas();  
  17.         init(s,pid,name,display,w,h,format,flags);  
  18.         mName = name;  
  19.     }  
  20.   
  21.     ......  
  22.   
  23.      public Canvas lockCanvas(Rect dirty) throws OutOfResourcesException, IllegalArgumentException  
  24.     {  
  25.         return lockCanvasNative(dirty);  
  26.     }  
  27.     public void unlockCanvasAndPost(Canvas canvas) {
  28.        nativeUnlockCanvasAndPost(mLockedObject, canvas);
  29.     }  
  30.     private native Canvas lockCanvasNative(Rect dirty);  
  31.     private native void nativeUnlockCanvasAndPost(long nativeObject, Canvas canvas);
  32.     ......  
  33. }  

Surface类有三个成员变量mSurfaceControl、mCanvas和mName,它们的类型分别是int、Canvas和mName,其中,mSurfaceControl保存的是jni层的一个SurfaceControl对象的地址值,mName用来描述当前正在创建的一个绘图表面的名称,每一个Surface对象内部都有一块画布,这块画布是通过它的成员变量mCanvas所指向的一个JCompatibleCanvas对象来描述的
Surface的成员函数lockCanvas调用lockCanvasNative来创建一块画布Canvas,它通过JNI方法获得一个图形缓冲区,并且将这个图形绘冲区封装在一块类型为Canvas的画布中返回给调用者使用,你可以 调用Canvas类所提供的绘图函数来绘制任意的UI, 绘制完成以后,通过Surface的成员函数 unlockCanvasAndPost()来调用JNI方法nativeUnlockCanvasAndPost() 将Canvas所描述的图形缓冲区提交给SurfaceFlinger服务处理

SurfaceView虽然具有独立的绘图表面,不过它仍然是宿主窗口的视图结构中的一个结点,因此,它仍然是可以参与到宿主窗口的绘制流程中去的, 如果要在一个Surface上进行UI绘制,那么就顺序执行以下的操作:
       (1). 在Surface的基础上获得一个Canvas对象。
       (2). 利用Canvas类提供的绘图接口在前面获得的画布上绘制任意的UI。
       (3). 将Canvas已经填充了UI数据的缓冲区提交给SurfaceFlinger服务,以便SurfaceFlinger服务可以合成到屏幕上去。

SurfaceView通过SurfaceHolder接口就可以执行上述的第(1)和引(3)个操作,示例代码如下所示:

SurfaceView sv = (SurfaceView )findViewById(R.id.surface_view);
SurfaceHolder sh = sv.getHolder(); 
Cavas canvas = sh.lockCanvas() 

//Draw something on canvas ...... 

sh.unlockCanvasAndPost(canvas);

SurfaceView类的成员函数getHolder将一个SurfaceHolder对象返回给调用者,而SurfaceHolder类的成员函数lockCanvas通过调用函数internalLockCanvas来在当前正在处理的SurfaceView的绘图表面上建立一块画布返回给调用者,看看SurfaceView的代码:

package android.view ;
public class SurfaceView extends View {
    ......
    final ReentrantLock mSurfaceLock = new ReentrantLock();
    final Surface mSurface = new Surface();
    ......
    private SurfaceHolder mSurfaceHolder = new SurfaceHolder() {
        ......

        public Canvas lockCanvas() {
            return internalLockCanvas(null);
        }

        ......

        private final Canvas internalLockCanvas(Rect dirty) {
              ....
            mSurfaceLock.lock();
            ......
            Canvas c = null;
            if (!mDrawingStopped && mWindow != null) {
                Rect frame = dirty != null ? dirty : mSurfaceFrame;
                try {
                    c = mSurface.lockCanvas(frame);
                } catch (Exception e) {
                    Log.e(LOG_TAG, "Exception locking surface", e);
                }
            }
            ......
            if (c != null) {
                mLastLockTime = SystemClock.uptimeMillis();
                return c;
            }
            ......
            mSurfaceLock.unlock();
            return null;
        }

        ......       

        public void unlockCanvasAndPost(Canvas canvas) {
            mSurface.unlockCanvasAndPost(canvas);
            mSurfaceLock.unlock();
        }

        ......
    }
    ……


    
@Override
public void draw(Canvas canvas) {
    if (mWindowType != WindowManager.LayoutParams. TYPE_APPLICATION_PANEL ) {

        if ((mPrivateFlags & PFLAG_SKIP_DRAW) == 0) {

            canvas.drawColor(0, PorterDuff.Mode. CLEAR );
        }
    }
    super.draw(canvas);
}

@Override
protected void dispatchDraw(Canvas canvas) {
    if (mWindowType != WindowManager.LayoutParams. TYPE_APPLICATION_PANEL ) {

        if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {

            canvas.drawColor(0, PorterDuff.Mode. CLEAR );
        }
    }
    super.dispatchDraw(canvas);
}
}

SurfaceHolder将当前正在处理的SurfaceView的Surface的Canvas返回给调用者访问和绘制,而这块画布不是线程安全的,因此,就需要对当前正在处理的SurfaceView的绘图表面Surface进行加锁保护,这是通过它的成员变量mSurfaceLock来实现的。

SurfaceView类继承自View,所以它的成员函数draw和dispatchDraw的参数canvas是宿主窗口的绘图表面上的画布分配的,因此,在这块画布上绘制的任何UI都是出现在宿主窗口的绘图表面上的,但是这里我们可以看到,如果当前正在处理的SurfaceView不是用作宿主窗口面板的时候,即其成员变量mWindowType的值不等于WindowManager.LayoutParams.TYPE_APPLICATION_PANEL的时候, SurfaceView类的成员函数draw只是简单地将它所占据的区域绘制为黑色。

 调用者在画布上绘制完成所需要的UI之后,就可以将这块画布的图形绘冲区的UI数据提交给SurfaceFlinger服务来处理了,这是通过调用SurfaceHolder类的成员函数unlockCanvasAndPost来实现的,它通过调用SurfaceView的成员变量mSurface的成员函数unlockCanvasAndPost来将参数canvas所描述的一块画布的图形缓冲区提交给SurfaceFlinger服务处理,以便SurfaceFlinger服务可以在合适的时候将该图形缓冲区合成到屏幕上去显示,这样就可以将对应的SurfaceView的UI展现出来了, 提交完成之后,再调用SurfaceView的成员变量mSurfaceLock来解锁当前正在处理的绘图表面,因为在前面我们曾经将该绘图表面锁住了。

总结来说SurfaceView有以下特点:

       1. 具有独立的绘图表面;

       2. 需要在宿主窗口上挖一个洞来显示自己;

       3. 它的UI绘制可以在独立的线程中进行,这样就可以进行复杂的UI绘制,并且不会影响应用程序的主线程响应用户输入;

       4.View主要适用于主动更新的情况下(调用invalidate),而SurfaceView主要适用于被动更新(在新线程不停绘制),例如频繁地刷新,如果自定义View需要频繁刷新,或者刷新时数据处理量比较大,就可以考虑使用SurfaceView来取代View了;

       5. View使用的是根部窗口的Surface在主线程中对画面进行刷新,而SurfaceView有自己的Surface通常会通过一个子线程来进行页面的刷新;

       6.SufaceView的定义和使用比View复杂,占用的资源也比较多,在使用View不能满足性能和速度的要求下再使用SurfaceView;

猜你喜欢

转载自blog.csdn.net/tugele/article/details/79199119