SurfaceView的个人理解

定义


普通的Android控件,比如Textview,button,Imageview等,它们的UI是在应用程序中的主线程中绘制的,与宿主窗口共享同一个绘图表面;我们都知道主线程除了绘制UI,还需要及时响应用户输入,否则,超过一段时间,就会弹ANR提示框。 而SurfaceView是Android里面一种特殊的视图,他拥有独立的视图表面,可以在一个独立的线程中进行绘制,不与其宿主窗口共享同一个绘图表面,可以进行高效复杂的UI绘制,并且不占用主线程;特别适合一些复杂的游戏画面,视频播放,摄像头预览的应用场景。
Android的应用程序是通过SurfaceFlinger服务来绘制自己的UI的。一般来说,每个窗口在SurfaceFlinger服务中都对应有一个Layer,用来描绘它的绘图表面。对于那些具有SurfaceView的窗口来说,每一个SurfaceView在SurfaceFlinger服务中还对应有一个独立的Layer或者LayerBuffer,它们都是以LayerBase为基类,用来单独描绘它的绘图表面,以区别于它的宿主窗口的绘图表面。


实现原理


Activity窗口的顶层视图DecorView和其他的控件的UI 都是绘制在SurfaceFlinger服务中的同一个Layer上面的,而SurfaceView的UI是绘制在SurfaceFlinger服务中的另外一个Layer或者LayerBuffer上的。注意,用来描述SurfaceView的Layer或者LayerBuffer的z轴位置是小于宿主Activity窗口的Layer的z轴位置的;前者会在后者的上面挖一个“洞”出来,实际上,SurfaceView在其宿主Activity窗口上设置了一块透明区域,以便它的UI可以对用户可见。
SurfaceView的具体实现过程,可分为一下三步:绘图表面的创建过程,在宿主窗口上面挖洞的过程,绘制过程。


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

由于SurfaceView具有独立的绘图表面,因此,在它的UI内容可以绘制之前,我们先要将它的绘图表面创建出来。尽管SurfaceView不与它的宿主窗口共享同一个绘图表面,但是它仍然是属于宿主窗口的视图结构的一个结点。他仍然是会参与到宿主窗口的某些执行流程中去。
每当一个窗口需要刷新UI时,就会调用ViewRoot类的成员函数performTraversals,
ViewRoot类的成员函数performTraversals在执行的过程中,如果发现当前窗口的绘图表面还没有创建或者失效,就会请求WindowManagerService服务创建一个新的绘图表面,同时,它还会通过一系列的回调函数来让嵌入在窗口里面的surfaceView有机会创建自己的绘图表面。
SurfaceView的绘图表面的创建过程,大致可分为以下八步:ViewRoot.performTraversals -> ViewGroup.dispatchAttachedToWindow -> View.dispatchAttachedToWindow -> SurfaceView.onAttachedToWindow ->
ViewGroup.dispathWindowVisibilityChanged -> View.dispathWindowVisibilityChanged -> SurfaceView.onWindowVisibilityChanged -> SurfaceView.updateWindow
在第一步中,首先分析ViewRoot类的成员函数performTraversals中四个相关的变量host 、attachInfo 、viewVisibility 和 viewVisibilityChanged。
变量host与ViewRoot类的成员变量mView指向的是同一个DecorView对象,即当前窗口的顶层视图。
变量attacheInfo与ViewRoot类的成员变量mAttachedInfo指向的是同一个AttachedInfo对象。在Android系统中,每一个视图附加到它的宿主窗口的时候,都会获得一个AttachInfo对象,用来描述被附加的窗口的信息。
变量viewVisibility描述的是当前窗口的可见性
变量viewVisibilityChanged描述的是当前窗口的可见性是否发生了变化。
…. frameworks/base/core/java/android/view/SurfaceView.java
第四步中, SurfaceView类的成员函数onAttachedToWindow做了两件重要的事.
第一件事情是通知父视图,当前正在处理的SurfaceView需要在宿主窗口的绘图表面上挖一个洞,即需要在宿主窗口的绘图表面上设置一块透明区域。当前正在处理的SurfaceView的父视图保存在父类View的成员变量mParent中,通过调用这个成员变量mParent所指向的一个ViewGroup对象的成员函数requestTransparentRegion,就可以通知到当前正在处理的SurfaceView的父视图,当前正在处理的SurfaceView需要在宿主窗口的绘图表面上设置一块透明区域。
第二件事情是调用从父类View继承下来的成员函数getWindowSession来获得一个实现了IWindowSession接口的Binder代理对象,并且将该Binder代理对象保存在SurfaceView类的成员变量mSession中。在Android系统中,每一个应用程序进程都有一个实现了IWindowSession接口的Binder代理对象,这个Binder代理对象是用来与WindowManagerService服务进行通信的,View类的成员函数getWindowSession返回的就是该Binder代理对象。在接下来的Step 8中,我们就可以看到,SurfaceView就可以通过这个实现了IWindowSession接口的Binder代理对象来请求WindowManagerService服务为自己创建绘图表面的。
这一步执行完成之后,返回到前面的Step 1中,即ViewRoot类的成员函数performTraversals中,我们假设当前窗口的可见性发生了变化,那么接下来就会调用顶层视图的成员函数dispatchWindowVisibilityChanged,以便可以通知各个子视图,它的宿主窗口的可见性发生化了。
第八步,在分析SurfaceView类的成员函数updateWindow的实现之前,我们首先介绍一些相关的成员变量的含义,其中,mSurface、mWindow、mWindowType和mRequestedType这四个成员变量是最重要的。
SurfaceView类的成员变量mSurface指向的是一个Surface对象,这个Surface对象描述的便是SurfaceView专有的绘图表面。对于一般的视图来说,例如,TextView或者Button,它们是没有专有的绘图表面的,而是与专宿主窗口共享同一个绘图表面,因此,它们就不会像SurfaceView一样,有一个专门的类型为Surface的成员变量来描述自己的绘图表面。
每一个Activity窗口都关联有一个W对象。这个W对象是一个实现了IWindow接口的Binder本地对象,它是用来传递给WindowManagerService服务的,以便WindowManagerService服务可以通过它来和它所关联的Activity窗口通信。例如,WindowManagerService服务通过这个W对象来通知它所关联的Activity窗口的大小或者可见性发生变化了。同时,这个W对象还用来在WindowManagerService服务这一侧唯一地标志一个窗口,也就是说,WindowManagerService服务会为这个W对象创建一个WindowState对象。
SurfaceView类的成员变量mWindow指向的是一个MyWindow对象。MyWindow类是从BaseIWindow类继承下来的,后者与W类一样,实现了IWindow接口。也就是说,每一个SurfaceView都关联有一个实现了IWindow接口的Binder本地对象,就如第一个Activity窗口都关联有一个实现了IWindow接口的W对象一样。从这里我们就可以推断出,每一个SurfaceView在WindowManagerService服务这一侧都对应有一个WindowState对象。从这一点来看,WindowManagerService服务认为Activity窗口和SurfaceView的地位是一样的,即认为它们都是一个窗口,并且具有绘图表面。接下来我们就会通过SurfaceView类的成员函数updateWindow的实现来证实这个推断。
SurfaceView类的成员变量mWindowType描述的是SurfaceView的窗口类型,它的默认值等于TYPE_APPLICATION_MEDIA。也就是说,我们在创建一个SurfaceView的时候,默认是用来显示多媒体的,例如,用来显示视频。SurfaceView还有另外一个窗口类型TYPE_APPLICATION_MEDIA_OVERLAY,它是用来在视频上面显示一个Overlay的,这个Overlay可以用来显示视字幕等信息。
如果一个Activity窗口嵌入有两个类型分别为TYPE_APPLICATION_MEDIA和TYPE_APPLICATION_MEDIA_OVERLAY的SurfaceView,那么该Activity窗口的Z轴位置大于类型为TYPE_APPLICATION_MEDIA_OVERLAY的SurfaceView的Z轴位置,而类型为TYPE_APPLICATION_MEDIA_OVERLAY的SurfaceView的Z轴位置又大于类型为TYPE_APPLICATION_MEDIA的窗口的Z轴位置。
注意,我们在创建了一个SurfaceView之后,可以调用它的成员函数setZOrderMediaOverlay、setZOrderOnTop或者setWindowType来修改该SurfaceView的窗口类型,也就是修改该SurfaceView的成员变量mWindowType的值。
SurfaceView类的成员变量mRequestedType描述的是SurfaceView的绘图表面的类型,一般来说,它的值可能等于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服务分配的,因而我们不能够在应用程序内部对它进行操作。例如,当一个SurfaceView是用来显示摄像头预览或者视频播放的时候,我们就会将它的绘图表面的类型设置为SURFACE_TYPE_PUSH_BUFFERS,这样摄像头服务或者视频播放服务就会为该SurfaceView绘图表面创建一块内存,并且将采集的预览图像数据或者视频帧数据源源不断地填充到该内存中去。注意,这块内存有可能是来自专用的硬件的,例如,它可能是来自视频卡的。在这种情况下,SurfaceFlinger服务使用一个LayerBuffer对象来描述该SurfaceView的绘图表面。
从上面的描述就得到一个重要的结论:绘图表面类型为SURFACE_TYPE_PUSH_BUFFERS的SurfaceView的UI是不能由应用程序来控制的,而是由专门的服务来控制的,例如,摄像头服务或者视频播放服务,同时,SurfaceFlinger服务会使用一种特殊的LayerBuffer来描述这种绘图表面。使用LayerBuffer来描述的绘图表面在进行渲染的时候,可以使用硬件加速,例如,使用copybit或者overlay来加快渲染速度,从而可以获得更流畅的摄像头预览或者视频播放。
注意,我们在创建了一个SurfaceView之后,可以调用它的成员函数getHolder获得一个SurfaceHolder对象,然后再调用该SurfaceHolder对象的成员函数setType来修改该SurfaceView的绘图表面的类型,即修改该SurfaceView的成员变量mRequestedType的值。
介绍完成SurfaceView类的成员变量mSurface、mWindow、mWindowType和mRequestedType的含义之后,我们再介绍其它几个接下来要用到的其它成员变量的含义:
–mHaveFrame,用来描述SurfaceView的宿主窗口的大小是否已经计算好了。只有当宿主窗口的大小计算之后,SurfaceView才可以更新自己的窗口。
–mRequestedWidth,用来描述SurfaceView最后一次被请求的宽度。
–mRequestedHeight,用来描述SurfaceView最后一次被请求的高度。
–mRequestedFormat,用来描述SurfaceView最后一次被请求的绘图表面的像素格式。
–mNewSurfaceNeeded,用来描述SurfaceView是否需要新创建一个绘图表面。
–mLeft、mTop、mWidth、mHeight,用来描述SurfaceView上一次所在的位置以及大小。
–mFormat,用来描述SurfaceView的绘图表面上一次所设置的格式。
–mVisible,用来描述SurfaceView上一次被设置的可见性。
–mType,用来描述SurfaceView的绘图表面上一次所设置的类型。
–mUpdateWindowNeeded,用来描述SurfaceView是否被WindowManagerService服务通知执行一次UI更新操作。
–mReportDrawNeeded,用来描述SurfaceView是否被WindowManagerService服务通知执行一次UI绘制操作。
–mLayout,指向的是一个WindowManager.LayoutParams对象,用来传递SurfaceView的布局参数以及属性值给WindowManagerService服务,以便WindowManagerService服务可以正确地维护它的状态。
理解了上述成员变量的含义的之后,接下来我们就可以分析SurfaceView类的成员函数updateWindow创建绘图表面的过程了,如下所示:
(1). 判断成员变量mHaveFrame的值是否等于false。如果是的话,那么就说明现在还不是时候为SurfaceView创建绘图表面,因为它的宿主窗口还没有准备就绪。
(2). 获得SurfaceView当前要使用的宽度和高度,并且保存在变量myWidth和myHeight中。注意,如果SurfaceView没有被请求设置宽度或者高度,那么就通过调用父类View的成员函数getWidth和getHeight来获得它默认所使用的宽度和高度。
(3). 调用父类View的成员函数getLocationInWindow来获得SurfaceView的左上角位置,并且保存在成员变量mLocation所描述的一个数组中。
(4). 判断以下条件之一是否成立:
–SurfaceView的绘图表面是否还未创建,即成员变量mWindow的值是否等于null;
–SurfaceView的绘图表面的像素格式是否发生了变化,即成员变量mFormat和mRequestedFormat的值是否不相等;
–SurfaceView的大小是否发生了变化,即变量myWidth和myHeight是否与成员变量mWidth和mHeight的值不相等;
–SurfaceView的可见性是否发生了变化,即成员变量mVisible和mRequestedVisible的值是否不相等,或者成员变量NewSurfaceNeeded的值是否等于true;
–SurfaceView的绘图表面的类型是否发生了变化,即成员变量mType和mRequestedType的值是否不相等;
–SurfaceView的位置是否发生了变化,即成员变量mLeft和mTop的值是否不等于前面计算得到的mLocation[0]和mLocation[1]的值;
–SurfaceView是否被WindowManagerService服务通知执行一次UI更新操作,即成员变量mUpdateWindowNeeded的值是否等于true;
–SurfaceView是否被WindowManagerService服务通知执行一次UI绘制操作,即成员变量mReportDrawNeeded的值是否等于true;
–SurfaceView类的成员函数updateWindow是否被调用者强制要求刷新或者绘制SurfaceView,即参数force或者redrawNeeded的值是否等于true。
只要上述条件之一成立,那么SurfaceView类的成员函数updateWindow就需要对SurfaceView的各种信息进行更新,即执行以下第5步至第7步操作。
(6). 检查成员变量mWindow的值是否等于null。如果等于null的话,那么就说明该SurfaceView还没有增加到WindowManagerService服务中去。在这种情况下,就会创建一个MyWindow对象保存在该成员变量中,并且调用成员变量mSession所描述的一个Binder代理对象的成员函数addWithoutInputChannel来将该MyWindow对象传递给WindowManagerService服务。在前面的Step 4中提到,SurfaceView类的成员变量mSession指向的是一个实现了IWindowSession接口的Binder代理对象,该Binder代理对象引用的是运行在WindowManagerService服务这一侧的一个Session对象。Session类的成员函数addWithoutInputChannel与另外一个成员函数add的实现是类似的,它们都是用来在WindowManagerService服务内部为指定的窗口增加一个WindowState对象,不过,Session类的成员函数addWithoutInputChannel只是在WindowManagerService服务内部为指定的窗口增加一个WindowState对象,而Session类的成员函数add除了会在WindowManagerService服务内部为指定的窗口增加一个WindowState对象之外,还会为该窗口创建一个用来接收用户输入的通道。
(7). 调用成员变量mSession所描述的一个Binder代理对象的成员函数relayout来请求WindowManagerService服务对SurfaceView的UI进行布局。WindowManagerService服务在对一个窗口进行布局的时候,如果发现该窗口的绘制表面还未创建,或者需要需要重新创建,那么就会为请求SurfaceFlinger服务为该窗口创建一个新的绘图表面,并且将该绘图表面返回来给调用者。在我们这个情景中,WindowManagerService服务返回来的绘图表面就会保存在成员变量mSurface。注意,这一步由于可能会修改SurfaceView的绘图表面,即修改成员变量mSurface的指向的一个Surface对象的内容,因此,就需要在获得成员变量mSurfaceLock所描述的一个锁的情况下执行,避免其它线程同时修改该绘图表面的内容,这是因为我们可能会使用一个独立的线程来来绘制SurfaceView的UI。
执行完成上述步骤之后,SurfaceView的绘图表面的创建操作就执行完成了,而当SurfaceView有了绘图表面之后,我们就可以使用独立的线程来绘制它的UI了,不过,在绘制之前,我们还需要在SurfaceView的宿主窗口上挖一个洞,以便绘制出来的UI不会被挡住。

  1. SurfaceView在宿主窗口上面的挖洞过程

SurfaceView的窗口类型一般都是TYPE_APPLICATION_MEDIA或者TYPE_APPLICATION_MEDIA_OVERLAY,也就是说,它的Z轴位置是小于其宿主窗口的Z位置的。为了保证SurfaceView的UI是可见的,SurfaceView就需要在其宿主窗口的上面挖一个洞出来,实际上就是在其宿主窗口的绘图表面上设置一块透明区域,以便可以将自己显示出来。
从SurfaceView的绘图表面的创建过程可以知道,SurfaceView在被附加到宿主窗口之上的时候,会请求在宿主窗口上设置透明区域,而每当其宿主窗口刷新自己的UI的时候,就会将所有嵌入在它里面的SurfaceView所设置的透明区域收集起来,然后再通知WindowManagerService服务为其设置一个总的透明区域。
从SurfaceView的绘图表面的创建过程可以知道,SurfaceView在被附加到宿主窗口之上的时候,SurfaceView类的成员函数onAttachedToWindow就会被调用。SurfaceView类的成员函数onAttachedToWindow在被调用的期间,就会请求在宿主窗口上设置透明区域。接下来,我们就从SurfaceView类的成员函数onAttachedToWindow开始,分析SurfaceView的挖洞过程。

  1. SurfaceView的绘制过程

  SurfaceView虽然具有独立的绘图表面,不过它仍然是宿主窗口的视图结构中的一个结点,因此,它仍然是可以参与到宿主窗口的绘制流程中去的。 窗口在绘制的过程中,每一个子视图的成员函数draw或者dispatchDraw都会被调用到,以便它们可以绘制自己的UI。

SurfaceView类的成员函数draw和dispatchDraw的实现如下所示:

    public class SurfaceView extends View {  
    ......  

    @Override  
    public void draw(Canvas canvas) {  
        if (mWindowType != WindowManager.LayoutParams.TYPE_APPLICATION_PANEL) {  
            // draw() is not called when SKIP_DRAW is set  
            if ((mPrivateFlags & SKIP_DRAW) == 0) {  
                // punch a whole in the view-hierarchy below us  
                canvas.drawColor(0, PorterDuff.Mode.CLEAR);  
            }  
        }  
        super.draw(canvas);  
    }  

    @Override  
    protected void dispatchDraw(Canvas canvas) {  
        if (mWindowType != WindowManager.LayoutParams.TYPE_APPLICATION_PANEL) {  
            // if SKIP_DRAW is cleared, draw() has already punched a hole  
            if ((mPrivateFlags & SKIP_DRAW) == SKIP_DRAW) {  
                // punch a whole in the view-hierarchy below us  
                canvas.drawColor(0, PorterDuff.Mode.CLEAR);  
            }  
        }  
        // reposition ourselves where the surface is   
        mHaveFrame = true;  
        updateWindow(false, false);  
        super.dispatchDraw(canvas);  
    }  

    ......  
}  

这两个函数定义在文件frameworks/base/core/java/android/view/SurfaceView.java中。
SurfaceView类的成员函数draw和dispatchDraw的参数canvas所描述的都是建立在宿主窗口的绘图表面上的画布,因此,在这块画布上绘制的任何UI都是出现在宿主窗口的绘图表面上的。
本来SurfaceView类的成员函数draw是用来将自己的UI绘制在宿主窗口的绘图表面上的,但是这里我们可以看到,如果当前正在处理的SurfaceView不是用作宿主窗口面板的时候,即其成员变量mWindowType的值不等于WindowManager.LayoutParams.TYPE_APPLICATION_PANEL的时候,SurfaceView类的成员函数draw只是简单地将它所占据的区域绘制为黑色。
本来SurfaceView类的成员函数dispatchDraw是用来绘制SurfaceView的子视图的,但是这里我们同样看到,如果当前正在处理的SurfaceView不是用作宿主窗口面板的时候,那么SurfaceView类的成员函数dispatchDraw只是简单地将它所占据的区域绘制为黑色,同时,它还会通过调用另外一个成员函数updateWindow更新自己的UI,实际上就是请求WindowManagerService服务对自己的UI进行布局,以及创建绘图表面,具体可以参考前面第1部分的内容。
从SurfaceView类的成员函数draw和dispatchDraw的实现就可以看出,SurfaceView在其宿主窗口的绘图表面上面所做的操作就是将自己所占据的区域绘为黑色,除此之外,就没有其它更多的操作了,这是因为SurfaceView的UI是要展现在它自己的绘图表面上面的。接下来我们就分析如何在SurfaceView的绘图表面上面进行UI绘制。
如果要在一个绘图表面进行UI绘制,那么就顺序执行以下的操作:
(1). 在绘图表面的基础上建立一块画布,即获得一个Canvas对象。
(2). 利用Canvas类提供的绘图接口在前面获得的画布上绘制任意的UI。
(3). 将已经填充好了UI数据的画布缓冲区提交给SurfaceFlinger服务,以便SurfaceFlinger服务可以将它合成到屏幕上去。
SurfaceView提供了一个SurfaceHolder接口,通过这个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);  

以上博客,均是看完Android视图SurfaceView的实现解析后,自己的归纳整理。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值