zoukankan      html  css  js  c++  java
  • Android之SurfaceView详解

     SurfaceView介绍
    通常情况程序的View和用户响应都是在同一个线程中处理的,这也是为什么处理长时间事件(例如访问网络)需要放到另外的线程中去(防止阻塞当前UI线程的操作和绘制)。但是在其他线程中却不能修改UI元素,例如用后台线程更新自定义View(调用View的在自定义View中的onDraw函数)是不允许的。
     
    如果需要在另外的线程绘制界面、需要迅速的更新界面或则渲染UI界面需要较长的时间,这种情况就要使用SurfaceView了。SurfaceView中包含一个Surface对象,而Surface是可以在后台线程中绘制的。Surface属于
     
    OPhone底层显示系统,关于这方面的介绍请参考附录中的资料[1]。SurfaceView的性质决定了其比较适合一些场景:需要界面迅速更新、对帧率要求较高的情况。使用SurfaceView需要注意以下几点情况:
    SurfaceView和SurfaceHolder.Callback函数都从当前SurfaceView窗口线程中调用(一般而言就是程序的主线程)。有关资源状态要注意和绘制线程之间的同步。 
     在绘制线程中必须先合法的获取Surface才能开始绘制内容,在SurfaceHolder.Callback.surfaceCreated() 和SurfaceHolder.Callback.surfaceDestroyed()之间的状态为合法的,另外在Surface类型为SURFACE_TYPE_PUSH_BUFFERS时候是不合法的。 
     额外的绘制线程会消耗系统的资源,在使用SurfaceView的时候要注意这点。 
     
    使用SurfaceView
    只要继承SurfaceView类并实现SurfaceHolder.Callback接口就可以实现一个自定义的SurfaceView了,SurfaceHolder.Callback在底层的Surface状态发生变化的时候通知View,SurfaceHolder.Callback具有如下的接口:
     surfaceCreated(SurfaceHolder holder):当Surface第一次创建后会立即调用该函数。程序可以在该函数中做些和绘制界面相关的初始化工作,一般情况下都是在另外的线程来绘制界面,所以不要在这个函数中绘制Surface。 
     surfaceChanged(SurfaceHolder holder, int format, int width,int height):当Surface的状态(大小和格式)发生变化的时候会调用该函数,在surfaceCreated调用后该函数至少会被调用一次。 
     surfaceDestroyed(SurfaceHolder holder):当Surface被摧毁前会调用该函数,该函数被调用后就不能继续使用Surface了,一般在该函数中来清理使用的资源。 
     
    通过SurfaceView的getHolder()函数可以获取SurfaceHolder对象,Surface 就在SurfaceHolder对象内。虽然Surface保存了当前窗口的像素数据,但是在使用过程中是不直接和Surface打交道的,由SurfaceHolder的Canvas lockCanvas()或则Canvas lockCanvas(Rect dirty)函数来获取Canvas对象,通过在Canvas上绘制内容来修改Surface中的数据。如果Surface不可编辑或则尚未创建调用该函数会返回null,在 unlockCanvas() 和 lockCanvas()中Surface的内容是不缓存的,所以需要完全重绘Surface的内容,为了提高效率只重绘变化的部分则可以调用lockCanvas(Rect dirty)函数来指定一个dirty区域,这样该区域外的内容会缓存起来。在调用lockCanvas函数获取Canvas后,SurfaceView会获取Surface的一个同步锁直到调用unlockCanvasAndPost(Canvas canvas)函数才释放该锁,这里的同步机制保证在Surface绘制过程中不会被改变(被摧毁、修改)。
    当在Canvas中绘制完成后,调用函数unlockCanvasAndPost(Canvas canvas)来通知系统Surface已经绘制完成,这样系统会把绘制完的内容显示出来。为了充分利用不同平台的资源,发挥平台的最优效果可以通过SurfaceHolder的setType函数来设置绘制的类型,目前接收如下的参数:
    SURFACE_TYPE_NORMAL:用RAM缓存原生数据的普通Surface 
    SURFACE_TYPE_HARDWARE:适用于DMA(Direct memory access )引擎和硬件加速的Surface 
    SURFACE_TYPE_GPU:适用于GPU加速的Surface 
    SURFACE_TYPE_PUSH_BUFFERS:表明该Surface不包含原生数据,Surface用到的数据由其他对象提供,在Camera图像预览中就使用该类型的Surface,有Camera负责提供给预览Surface数据,这样图像预览会比较流畅。如果设置这种类型则就不能调用lockCanvas来获取Canvas对象了。 
     

    SurfaceView和View最本质的区别

    SurfaceView和View最本质的区别在于,surfaceView是在一个新起的单独线程中可以重新绘制画面而View必须在UI的主线程中更新画面。
    那么在UI的主线程中更新画面 可能会引发问题,比如你更新画面的时间过长,那么你的主UI线程会被你正在画的函数阻塞。那么将无法响应按键,触屏等消息。
    当使用surfaceView 由于是在新的线程中更新画面所以不会阻塞你的UI主线程。但这也带来了另外一个问题,就是事件同步。比如你触屏了一下,你需要surfaceView中thread处理,一般就需要有一个event queue的设计来保存touch event,这会稍稍复杂一点,因为涉及到线程同步。

    所以基于以上,根据游戏特点,一般分成两类。

    1 被动更新画面的。比如棋类,这种用view就好了。因为画面的更新是依赖于 onTouch 来更新,可以直接使用 invalidate。 因为这种情况下,这一次Touch和下一次的Touch需要的时间比较长些,不会产生影响。

    2 主动更新。比如一个人在一直跑动。这就需要一个单独的thread不停的重绘人的状态,避免阻塞main UI thread。所以显然view不合适,需要surfaceView来控制。
     

    周末看《精通Android游戏开发》(Pro Android Games),里面讲到游戏的框架,其中一个重要的概念surfaceview,觉得不是很理解,于是花了一点时间研究了下,写下自己的心得。

    surface,这个单词的意思是浮在表面的,那么surfaceview就是浮在表面的view了。如果真的这样解释,估计有人要拍砖了。然而,话虽不能这么说,取这个名儿,多少还是有点关系的。surface是一个可见区域。


    我们在屏幕上看到的这些view,在屏幕上看到的就是画面,在内存中就是一块内存区。绘图的时候,就是显示的硬件如显卡将内存区的这块图形数据绘制到屏幕上。所以,从内存的角度去看这些东西,会比较好理解。


    surface是surfaceview中的一个可见部分。我们知道,我们看到的屏幕上的图形,是二维的,我们看到的就是长和宽,其实,在内部实际上是三维的,另一个维度,就是层layer。我们用visio绘图,都会看到这种情况,一个图形会将另个图形遮住,是因为这个图形在上层。如果有同AutoCAD的经验,对这个更容易理解。我们看到的图形实际上是很多图形一层层的叠加在一起的,这些图形元素完全不可见,或者部分可见部分不可见,或者完全可见。


    这样看来,surface就可以这样理解:它是内存中一块区域,它是surfaceview可见不那个部分,绘图操作作用于它,然后它就会被显卡之类的显示控制器绘制到屏幕上。


    surface是个啥,大概已经有了些概念了。因为它对应了一个内存区,大家都知道,内存区的对象是有生命周期的,可以动态的申请创建和销毁,当然也可能会更新。于是,就有了作用于这个内存区的操作,这些操作就是surfaceCreated/Changed/Destroyed。三个操作放在一起,就是callback,
    所以在很多例子里看到,会有callback。


    callback,是回调,意思是自己能干一些活,不过自己不去主动做,而是到别人那里去登记一下,别人需要的时候,就会叫我去做。就这个例子而言,可以打这个比方,某建筑工人队伍A,能盖房子,能装修,也能拆房子。可是他自己不去主动的做这些事情,而是去向开发商B去登记这三种能力,当然了,他把这三种能力打了包,叫做A的能力。啥时候,B有活干了,就叫A去做,或者是盖房子,或者是装修,或者是拆建筑。在这个例子中,能力包就是callback.


    说了这个例子,其实就解释了 surface相关的一些东西,callback已经说过了,下面来说说其他的。假设surface就是一栋房子,那么surface拥有surfaceHolder,谁呢?在这个例子中好比建筑队A。盖房子对应的就是surfaceCreated, 拆房子就对应了surfaceDestroyed,装修就对应了surfaceChanged.


    surface有生存期,好比房子有生存期,在建造以后就存在,在拆了之后就没有了。装修必须发生在这之间。同样的,surface的change必须发生在created和destroyed之间。


    surfaceview知道surface的holder是谁,在surfaceview生成的时候,会调用getHolder得到holder,然后holder会调用addCallback将三个callback函数注册。


    holder拥有对于surface的控制权。在很多程序中,会在surfaceCreated的函数实现中创建另一个线程。所以在这里有两个线程,一个是UI线程,另一个负责画图的线程。画图线程由UI线程调用surfaceCreated的时候创建,在surfaceDestroyed调用的时候放回到线程池。在这中间,画图线程负责图形的绘制。


    在这种模型下,UI线程和画图线程各司其职,前者主要负责和用户的交互,而后者,在负责绘制图形。这样,绘制图形的时候如果时间较长,不会阻塞用户的输入。


    我们知道,线程共享内存数据,所以, surface对于两个线程是共享的。所以,为了避免在画图的时候,UI线程也对surface进行操作,在画图前,需要对surface加锁。这个工作是有holder干的,holder会先锁住surface中的一块holder.lockCanvas,我们叫canvas,然后,在上面绘画,画完之后,会解锁unlockCanvasAndPost。


    在 应用中,画图可以是一次性的,也可以是由定时器触发的定时的画。实现的都是runnable类中的run方法。关于runnable类,这个Java中定义的,并不是android独有的,可以参考Java的referrence.


    这个模型最大的好处就是,画图不依赖于UI线程,不会阻塞UI线程。
    而单纯的view是依赖于UI线程画图的。对于完全依赖于用户的输入进行图像显示的更新的,用view是可以的,但是如果能够自动的进行绘图,而不需等待用户的输入,surfaceview无疑是更好的选择。


    有图有真相,请看图\


     

     

     

     

    在android中开发游戏,一般来说,或想写一个复杂一点的游戏,是必须用到SurfaceView来开发的。
    经过这一阵子对android的学习,我找到了自已在android中游戏开发的误区,不要老想着用Layout和view去实现,不要将某个游戏
    中的对象做成一个组件来处理。应该尽量想着在Canvas(画布)中画出游戏戏中的背景、人物、动画等...
    SurfaceView提供直接访问一个可画图的界面,可以控制在界面顶部的子视图层。SurfaceView是提供给需要直接画像素而不是使用
    窗体部件的应用使用的。Android图形系统中一个重要的概念和线索是surface。View及其子类(如TextView, Button)
    要画在surface上。每个surface创建一个Canvas对象(但属性时常改变),用来管理view在surface上的绘图操作,如画点画线。
    还要注意的是,使用它的时候,一般都是出现在最顶层的:The view hierarchy will take care of correctly compositing
    with the Surface any siblings of the SurfaceView that would normally appear on top of it.
    使用的SurfaceView的时候,一般情况下还要对其进行创建,销毁,改变时的情况进行监视,这就要用到SurfaceHolder.Callback.
    class BBatt extends SurfaceView implements SurfaceHolder.Callback {
    public void surfaceChanged(SurfaceHolder holder,int format,int width,int height){}
    //看其名知其义,在surface的大小发生改变时激发
    public void surfaceCreated(SurfaceHolder holder){}
    //同上,在创建时激发,一般在这里调用画图的线程。
    public void surfaceDestroyed(SurfaceHolder holder) {}
    //同上,销毁时激发,一般在这里将画图的线程停止、释放。
    }


     
  • 相关阅读:
    vs2010中如何编译dll
    EM图解
    二级指针代替二维数组传入参数
    常見算法的穩定性
    Opencv +vs2010的问题之0x000007b
    Linux C程序设计大全之gdb学习
    makefile学习
    python之如何share你的module
    socket与http的区别
    最完美的单例实现
  • 原文地址:https://www.cnblogs.com/xuewater/p/2833416.html
Copyright © 2011-2022 走看看