SurfaceView的工作原理

大家在做android游戏和视频播放器的apk开发时,会经常用到SurfaceView这个类,以及它的子类VideoView,GLSurfaceView等.它与普通的View视图有本质的区别,本文就对SurfaceView的工作原理做个详细介绍(阅读本文之前,你需要对view的绘制流程,WMS,SurfaceFlinger的工作原理有一定了解).

SurfaceView不与它的宿主窗口共享同一个绘图表面,它拥有自己独立的绘图表面,也就是说拥有独立的Surface本地窗口,它可以在一个独立的子线程中进行绘制,这样它的绘制操作就不会阻塞主线程,当然它也可以在主线程中进行绘制,而其它的普通ui控件都是在应用程序的主线程中进行绘制的。

SurfaceView类的成员变量mWindowType描述的是SurfaceView的窗口类型,它的默认值等于TYPE_APPLICATION_MEDIA。也就是说,我们在创建一个SurfaceView的时候,默认是用来显示多媒体的,例如,用来显示视频。SurfaceView还有另外一个窗口类型TYPE_APPLICATION_MEDIA_OVERLAY,它是用来在视频上面显示一个Overlay的,这个Overlay可以用来显示视字幕等信息。

我们在创建了一个SurfaceView之后,可以调用它的成员函数setZOrderMediaOverlaysetZOrderOnTop或者setWindowType来修改该SurfaceView的窗口类型,也就是修改该SurfaceView的成员变量mWindowType的值。SurfaceView一般都是位于它对应的宿主窗口之下,也就是对应的layerzorder值更小,这样便于在SurfaceView的上面显示其它ui和字幕等信息,由于SurfaceView在其宿主窗口下面,这就要求必须在其宿主窗口界面上“挖洞”来显示SurfaceView内容,挖洞的实质就是在宿主窗口界面中对应显示SurfaceView的那块区域位置设置为透明区域。设置透明区域的代码调用流程为:

ViewRootImpl::performTraversals()->DecorView::dispatchAttachedToWindow()->View::dispatchAttachedToWindow()->SurfaceView::onAttachedToWindow()

该流程表示当它的宿主窗口第一次被绘制的时候,会调用到SurfaceViewonAttachedToWindow(),就是将SurfaceView添加到它的宿主窗口中去,onAttachedToWindow中会调用mParent.requestTransparentRegion(),该方法表示请求在其宿主窗口的视图上“挖洞”来显示SurfaceView的内容,也就是在宿主窗口的视图上设置一块透明区域。透明区域的计算调用流程为:ViewRootImpl::performTraversals()->host.gatherTransparentRegion(mTransparentRegion);hostDecorView,该方法最终会调用到SurfaceViewgatherTransparentRegion(),它用于收集透明区域。在performTraversals()中最后还会调用mWindowSession.setTransparentRegion(mWindow,mTransparentRegion);将最终计算的透明区域传递给WMS,然后WMS又传递给SurfaceFlinger,它的调用流程为:ViewRootImpl::performTraversals()->Session::setTransparentRegion()->WindowManagerService::setTransparentRegionWindow()->WindowStateAnimator::setTransparentRegionHintLocked()->SurfaceControl::setTransparentRegionHint()->SurfaceComposerClient::setTransparentRegionHint()->SurfaceFlinger::setTransactionState()->SurfaceFlinger::setClientStateLocked()->layer::setTransparentRegionHint(),在使用一个视频播放器播放一个视频文件时,使用dumpsys SF可以看到类似如下输出:

Layer 0xb7fc8b60 (SurfaceView) //SurfaceView播放视频

Region transparentRegion(this=0xb7fc8cc4, count=1)

[ 0, 0, 0, 0]

Region visibleRegion (this=0xb7fc8b68,count=1)

[ 0, 0, 1280, 720]

+ Layer 0xb7fc81c8(***/***.PlayActivity)

RegiontransparentRegion(this=0xb7fc832c, count=1) //设置的透明区域大小

[ 0, 0, 1280, 720]//播放器的activitylayer值大于SurfaceView,SurfaceView要显示,需求请求系统将其宿主窗口对应的区域设置为透明。

ViewRootImpl中与dispatchAttachedToWindow()对应的方法是dispatchDetachedFromWindow(),在View中分别调用到onAttachedToWindow()onDetachedFromWindow(),用于表示当前视图从宿主窗口中分离开来,但是该方法并不会一直调用到,其调用流程为:ActivityThread::handleDestroyActivity()->WindowManagerGlobal::removeView()->ViewRootImpl::die()->ViewRootImpl::doDie()->ViewRootImpl::dispatchDetachedFromWindow()->View::onDetachedFromWindow(),从流程中可以看出只有销毁对应的Activity时(触发AcvitityonDestroy())才会触发对应的ViewonDetachedFromWindow()

SurfaceView使用的绘图表面类型为SURFACE_TYPE_NORMALSURFACE_TYPE_PUSH_BUFFERS,当为SURFACE_TYPE_NORMAL时,它使用的是一块普通内存,一般由SurfaceFlinger分配内存,应用程序可以访问它,可以在它上面填充任意ui数据,当类型为SURFACE_TYPE_PUSH_BUFFERS时,表示SurfaceViewSurface对应的内存不是由SurfaceFlinger分配,应用程序不能访问它对应的内存。这种场景一般在使用摄像头预览或视频播放场景,摄像头预览服务或视频播放服务会为该SurfaceView对应的Surface分配内存,也有可能是直接由硬件来分配内存,并将预览图像数据或视频帧数据不断的填充到对应的内存中去。绘图表面类型为SURFACE_TYPE_PUSH_BUFFERSSurfaceViewUI是不能由应用程序来控制的,而是由专门的服务来控制的,在android4.4surface的类型定义在SurfaceHolder.java中,总共有四种取值:SURFACE_TYPE_NORMALSURFACE_TYPE_HARDWARESURFACE_TYPE_GPUSURFACE_TYPE_PUSH_BUFFERS,由系统自行设置,不过在代码中没有找到调用SURFACE_TYPE_HARDWARESURFACE_TYPE_GPU的地方,只在BaseSurfaceHolder中有说明SURFACE_TYPE_HARDWARESURFACE_TYPE_GPU看做SURFACE_TYPE_NORMAL

SurfaceView虽然有自己的本地窗口,但是在对应WindowManagerService中有自己的窗口WindowState,每个SurfaceViewWMS中都有一个WindowState对象,代码实现在SurfaceViewupdateWindow(),关键代码如下:

if (mWindow == null) { //MyWindow的类型为MyWindow,它指向IWindow.Stub

Display display =getDisplay();

mWindow = newMyWindow(this);

mLayout.type =mWindowType;

mLayout.gravity =Gravity.START|Gravity.TOP;

mSession.addToDisplayWithoutInputChannel(mWindow,mWindow.mSeq, mLayout,

mVisible ?VISIBLE : GONE, display.getDisplayId(), mContentInsets); //mSessionIWindowSessin类型,它为WMS中的Session在客户端的代理,表示窗口会话,用于和WMS进行跨进程通信,该行语句的作用表示请求WMS中创建一个窗口,它与addToDisplay()的区别在于,它不会创建输入通道,因为它不需要接收和处理按键消息,该方法执行完后,在WMS中会创建一个WindowState对象。

}

SurfaceViewSurfaceFlinger这边对应一个Layer,它的创建过程是在SurfaceViewupdateWindow()中,有如下代码:relayoutResult=mSession.relayout();这个通过binder调用到WMS中的relayoutWindow(),该方法中会创建一个SurfaceControl对象,SurfaceControl通过jni以及binder通信,最终在SurfaceFlinger中创建一个Layer对象。注意在调用relayout()的方法中,最后一个参数为mNewSurface,执行结束后,它会指向一个native层的Surface对象。再调用mSurface.transferFrom(mNewSurface);这样mSurface就指向了之前native层的Surface对象。

SurfaceView类的成员函数drawdispatchDraw的参数canvas所描述的都是建立在宿主窗口的绘图表面上的画布,

public void draw(Canvas canvas){//此处的canvas指向SurfaceView的宿主窗口,而不是SurfaceView本身的窗口。

if (mWindowType !=WindowManager.LayoutParams.TYPE_APPLICATION_PANEL) {

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

canvas.drawColor(0,PorterDuff.Mode.CLEAR); //在其宿主窗口上画黑色,但不会提交到画布上。

}

}

super.draw(canvas);

}


版权声明:本文为zhanxiaoqi原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。