大家在做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之后,可以调用它的成员函数setZOrderMediaOverlay、setZOrderOnTop或者setWindowType来修改该SurfaceView的窗口类型,也就是修改该SurfaceView的成员变量mWindowType的值。SurfaceView一般都是位于它对应的宿主窗口之下,也就是对应的layer的zorder值更小,这样便于在SurfaceView的上面显示其它ui和字幕等信息,由于SurfaceView在其宿主窗口下面,这就要求必须在其宿主窗口界面上“挖洞”来显示SurfaceView内容,挖洞的实质就是在宿主窗口界面中对应显示SurfaceView的那块区域位置设置为透明区域。设置透明区域的代码调用流程为:
ViewRootImpl::performTraversals()->DecorView::dispatchAttachedToWindow()->View::dispatchAttachedToWindow()->SurfaceView::onAttachedToWindow()。
该流程表示当它的宿主窗口第一次被绘制的时候,会调用到SurfaceView的onAttachedToWindow(),就是将SurfaceView添加到它的宿主窗口中去,onAttachedToWindow中会调用mParent.requestTransparentRegion(),该方法表示请求在其宿主窗口的视图上“挖洞”来显示SurfaceView的内容,也就是在宿主窗口的视图上设置一块透明区域。透明区域的计算调用流程为:ViewRootImpl::performTraversals()->host.gatherTransparentRegion(mTransparentRegion);host为DecorView,该方法最终会调用到SurfaceView的gatherTransparentRegion(),它用于收集透明区域。在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时(触发Acvitity的onDestroy())才会触发对应的View的onDetachedFromWindow()。
SurfaceView使用的绘图表面类型为SURFACE_TYPE_NORMAL或SURFACE_TYPE_PUSH_BUFFERS,当为SURFACE_TYPE_NORMAL时,它使用的是一块普通内存,一般由SurfaceFlinger分配内存,应用程序可以访问它,可以在它上面填充任意ui数据,当类型为SURFACE_TYPE_PUSH_BUFFERS时,表示SurfaceView的Surface对应的内存不是由SurfaceFlinger分配,应用程序不能访问它对应的内存。这种场景一般在使用摄像头预览或视频播放场景,摄像头预览服务或视频播放服务会为该SurfaceView对应的Surface分配内存,也有可能是直接由硬件来分配内存,并将预览图像数据或视频帧数据不断的填充到对应的内存中去。绘图表面类型为SURFACE_TYPE_PUSH_BUFFERS的SurfaceView的UI是不能由应用程序来控制的,而是由专门的服务来控制的,在android4.4中surface的类型定义在SurfaceHolder.java中,总共有四种取值:SURFACE_TYPE_NORMAL、SURFACE_TYPE_HARDWARE、SURFACE_TYPE_GPU、SURFACE_TYPE_PUSH_BUFFERS,由系统自行设置,不过在代码中没有找到调用SURFACE_TYPE_HARDWARE和SURFACE_TYPE_GPU的地方,只在BaseSurfaceHolder中有说明SURFACE_TYPE_HARDWARE和SURFACE_TYPE_GPU看做SURFACE_TYPE_NORMAL。
SurfaceView虽然有自己的本地窗口,但是在对应WindowManagerService中有自己的窗口WindowState,每个SurfaceView在WMS中都有一个WindowState对象,代码实现在SurfaceView的updateWindow()中,关键代码如下:
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); //mSession为IWindowSessin类型,它为WMS中的Session在客户端的代理,表示窗口会话,用于和WMS进行跨进程通信,该行语句的作用表示请求WMS中创建一个窗口,它与addToDisplay()的区别在于,它不会创建输入通道,因为它不需要接收和处理按键消息,该方法执行完后,在WMS中会创建一个WindowState对象。
}
SurfaceView在SurfaceFlinger这边对应一个Layer,它的创建过程是在SurfaceView的updateWindow()中,有如下代码:relayoutResult=mSession.relayout();这个通过binder调用到WMS中的relayoutWindow(),该方法中会创建一个SurfaceControl对象,SurfaceControl通过jni以及binder通信,最终在SurfaceFlinger中创建一个Layer对象。注意在调用relayout()的方法中,最后一个参数为mNewSurface,执行结束后,它会指向一个native层的Surface对象。再调用mSurface.transferFrom(mNewSurface);这样mSurface就指向了之前native层的Surface对象。
SurfaceView类的成员函数draw和dispatchDraw的参数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);
}