首先,我们看看SurfaceView的官方介绍:

Provides a dedicated drawing surface embedded inside of a view hierarchy. You can control the format of this surface and, if you like, its size; the SurfaceView takes care of placing the surface at the correct location on the screen

在View的层次结构中,嵌入了一个用于画图的Surface,你可以控制这个Surface的格式,像大小;SurfaceView负责将Surface放置在屏幕上的正确位置。

One of the purposes of this class is to provide a surface in which a secondary thread can render into the screen. If you are going to use it this way, you need to be aware of some threading semantics:

这个类的其中一个目的就是提供一个Surface,它可以在第二个线程中渲染到屏幕。如果你打算使用这个,那么需要主要:

All SurfaceView and SurfaceHolder.Callback methods will be called from the thread running the SurfaceView’s window (typically the main thread of the application). They thus need to correctly synchronize with any state that is also touched by the drawing thread.

所有的SurfaceView和SurfaceView.Callback方法都运行SurfaceView的那个窗口的线程被调用。因此,它们需要与绘图线程也触及的任何状态正确同步

You must ensure that the drawing thread only touches the underlying Surface while it is valid – between SurfaceHolder.Callback.surfaceCreated() and SurfaceHolder.Callback.surfaceDestroyed().

你必须确保绘图线程仅在其有效时触及底层Surface。在SurfaceHolder.Callback.surfaceCreated() 和 SurfaceHolder.Callback.surfaceDestroyed()之间。

也就是说,SurfaceView拥有独立的绘图表面Surface,即它不与其宿主窗口共享一个Surface。由于拥有独立的Surface,因此SurfaceView的UI就可以在一个单独的线程中进行绘制。
对于Android普通的控件,例如TextView,Button等,它们都是将自己的UI绘制在宿主窗口的绘图表面上,这意味着它们的UI在是应用线程的主线程中进行绘制的。当我们对UI绘制的操作花费很多时间,就很有可能被系统认为应用程序没有响应了,因此就会弹出一个ANR对话框出来。

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

从Java层的UI绘制过程中,我们可以知道,当要需要绘制UI时,会调用ViewRootImpl的performTraversals方法。在该方法中,如果发现当前窗口的绘图表面还没有创建或者是无效的,就会请求WindowManagerService去创建一个新的绘图表面,同时它会通过一系列的回调方法来让嵌入在窗口里面的SurfaceView有机会创建自己的绘图表面。

private void performTraversals() {
        // cache mView since it is used so much below...
        final View host = mView;
       ... ...
        mIsInTraversal = true;
        mWillDrawSoon = true;
       ... ...
        WindowManager.LayoutParams lp = mWindowAttributes;
       ... ...
        final int viewVisibility = getHostVisibility();
        final boolean viewVisibilityChanged = !mFirst
                && (mViewVisibility != viewVisibility || mNewSurfaceNeeded
                // Also check for possible double visibility update, which will make current
                // viewVisibility value equal to mViewVisibility and we may miss it.
                || mAppVisibilityChanged);
        ... ...
        if (mFirst) {
            ... ...
           
            host.dispatchAttachedToWindow(mAttachInfo, 0);
            mAttachInfo.mTreeObserver.dispatchOnWindowAttachedChange(true);
           ... ...
        } else {
           ... ...
        }
        if (viewVisibilityChanged) {
            mAttachInfo.mWindowVisibility = viewVisibility;
            host.dispatchWindowVisibilityChanged(viewVisibility);
           ... ...
        }
        ... ...
        mFirst = false;
       ... ...
        mIsInTraversal = false;
    }

我们先看一下这几个变量的含义:

  • host由mView赋值,指向一个DecorView
  • mAttachInfo,在Android系统中,每一个视图附加到它的宿主窗口的时候,都会获得一个AttachInfo对象,用来描述被附加的窗口的信息
  • viewVisibility 描述的是当前窗口的可见性
  • viewVisibilityChanged描述的是当前窗口的可见性是否发生了变化

ViewRootImpl类的成员变量mFirst表示当前窗口是否是第一次UI,如果是的话,它的值就会为true,这个时候,当前窗口的Surface没有创建,这个时候,就会调用host.dispatchAttachedToWindow(mAttachInfo, 0);来通知每一个子视图要被附加到宿主窗口上了。
接下来,检查viewVisibilityChanged的值是否为true,如果发生了变化,就会从当前窗口的顶层视图开始,通知每一个子视图它的宿主窗口的可见性发生了变化。这是通过调用host.dispatchWindowVisibilityChanged(viewVisibility);来实现的。

我们先看host.dispatchAttachedToWindow(mAttachInfo, 0)
我们知道host指向的是一个DecorView对象,这个方法,DecorView没有重写,它的父类FrameLayout也没有重写,因此它在ViewGroup中:

@Override
    void dispatchAttachedToWindow(AttachInfo info, int visibility) {
        mGroupFlags |= FLAG_PREVENT_DISPATCH_ATTACHED_TO_WINDOW;
        super.dispatchAttachedToWindow(info, visibility);
        mGroupFlags &= ~FLAG_PREVENT_DISPATCH_ATTACHED_TO_WINDOW;

        final int count = mChildrenCount;
        final View[] children = mChildren;
        for (int i = 0; i < count; i++) {
            final View child = children[i];
            child.dispatchAttachedToWindow(info,
                    combineVisibility(visibility, child.getVisibility()));
        }
        final int transientCount = mTransientIndices == null ? 0 : mTransientIndices.size();
        for (int i = 0; i < transientCount; ++i) {
            View view = mTransientViews.get(i);
            view.dispatchAttachedToWindow(info,
                    combineVisibility(visibility, view.getVisibility()));
        }
    }

在上面方法中,我们可以看到,它会循环遍历每一个子View,调用它的dispatchAttachedToWindow方法,如果现在视图中有一个SurfaceView,那么就会调用到它的dispatchAttachedToWindow方法:SurfaceView没有重写这个方法,它的父类View中有:

void dispatchAttachedToWindow(AttachInfo info, int visibility) {
//获得附加窗口的信息
        mAttachInfo = info;
        
       ... ...
        onAttachedToWindow();

       ... ...
    }

在上面的方法中我们主要看onAttachedToWindow方法,这个方法在SurfaceView中重写了:

@Override
//SurfaceView.java
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();

        getViewRootImpl().addWindowStoppedCallback(this);
        mWindowStopped = false;

        mViewVisibility = getVisibility() == VISIBLE;
        updateRequestedVisibility();
        mAttachedToWindow = true;
        //要求一块透明的区域
        mParent.requestTransparentRegion(SurfaceView.this);
        if (!mGlobalListenersAdded) {
            ViewTreeObserver observer = getViewTreeObserver();
            observer.addOnScrollChangedListener(mScrollChangedListener);
            observer.addOnPreDrawListener(mDrawListener);
            mGlobalListenersAdded = true;
        }
    }

上面方法的主要作用就是通知父视图,当前正在处理的SurfaceView需要在宿主窗口的绘图表面上设置一块透明区域。

这个方法执行完后,回到ViewRootImpl的performTraversals方法中,我们假设当前窗口的可见性发生了变化,那么就会调用host.dispatchWindowVisibilityChanged(viewVisibility):同样这个方法在ViewGroup中:

@Override
    public void dispatchWindowVisibilityChanged(int visibility) {
        super.dispatchWindowVisibilityChanged(visibility);
        final int count = mChildrenCount;
        final View[] children = mChildren;
        for (int i = 0; i < count; i++) {
            children[i].dispatchWindowVisibilityChanged(visibility);
        }
    }

同样,遍历每一个子View,调用子View的dispatchWindowVisibilityChanged方法:同样,方法在SurfaceView的父类View中:

public void dispatchWindowVisibilityChanged(@Visibility int visibility) {
        onWindowVisibilityChanged(visibility);
    }

可以看到它只是调用了onWindowVisibilityChanged,这个方法在SurfceVIew中重写了:

@Override
    protected void onWindowVisibilityChanged(int visibility) {
        super.onWindowVisibilityChanged(visibility);
        mWindowVisibility = visibility == VISIBLE;
        updateRequestedVisibility();
        updateSurface();
    }

private void updateRequestedVisibility() {
        mRequestedVisible = mViewVisibility && mWindowVisibility && !mWindowStopped;
    }

我们主要看updateSurface()方法:、

final ReentrantLock mSurfaceLock = new ReentrantLock();
 final Surface mSurface = new Surface();       // Current surface in use
 SurfaceSession mSurfaceSession;
 SurfaceControl mSurfaceControl;
 int mSubLayer = APPLICATION_MEDIA_SUBLAYER;
 protected void updateSurface() {
        if (!mHaveFrame) {
            return;
        }
        ViewRootImpl viewRoot = getViewRootImpl();
        if (viewRoot == null || viewRoot.mSurface == null || !viewRoot.mSurface.isValid()) {
            return;
        }

       ... ...
        int myWidth = mRequestedWidth;
        if (myWidth <= 0) myWidth = getWidth();
        int myHeight = mRequestedHeight;
        if (myHeight <= 0) myHeight = getHeight();

        final boolean formatChanged = mFormat != mRequestedFormat;
        final boolean visibleChanged = mVisible != mRequestedVisible;
        final boolean creating = (mSurfaceControl == null || formatChanged || visibleChanged)
                && mRequestedVisible;
        final boolean sizeChanged = mSurfaceWidth != myWidth || mSurfaceHeight != myHeight;
        final boolean windowVisibleChanged = mWindowVisibility != mLastWindowVisibility;
        boolean redrawNeeded = false;
		//判断是否SurfaceView有变化
        if (creating || formatChanged || sizeChanged || visibleChanged || windowVisibleChanged) {
            getLocationInWindow(mLocation);

            ... ...

            try {
                final boolean visible = mVisible = mRequestedVisible;
                ... ...

                if (creating) {
                //创建SurfaceSession对象,这个对象连接这SurfaceFlinger
                    mSurfaceSession = new SurfaceSession(viewRoot.mSurface);
                    mDeferredDestroySurfaceControl = mSurfaceControl;

                    updateOpaqueFlag();
                    //创建SurfaceControlWithBackground对象,其实就是SurfaceControl对象
                    mSurfaceControl = new SurfaceControlWithBackground(mSurfaceSession,
                            "SurfaceView - " + viewRoot.getTitle().toString(),
                            mSurfaceWidth, mSurfaceHeight, mFormat,
                            mSurfaceFlags);
                } else if (mSurfaceControl == null) {
                    return;
                }

                boolean realSizeChanged = false;
				//保证同步
                mSurfaceLock.lock();
                try {
                    mDrawingStopped = !visible;

                   ... ...

                    try {
                        mSurfaceControl.setLayer(mSubLayer);
                        if (mViewVisibility) {
                            mSurfaceControl.show();
                        } else {
                            mSurfaceControl.hide();
                        }

                        if (sizeChanged || creating || !mRtHandlingPositionUpdates) {
                            //设置位置
                            mSurfaceControl.setPosition(mScreenRect.left, mScreenRect.top);
                            mSurfaceControl.setMatrix(mScreenRect.width() / (float) mSurfaceWidth,
                                    0.0f, 0.0f,
                                    mScreenRect.height() / (float) mSurfaceHeight);
                        }
                        if (sizeChanged) {
                            mSurfaceControl.setSize(mSurfaceWidth, mSurfaceHeight);
                        }
                    } finally {
                      ... ...
                    }

                    if (sizeChanged || creating) {
                        redrawNeeded = true;
                    }

                    ... ...
                } finally {
                    mSurfaceLock.unlock();
                }

                try {
                    redrawNeeded |= visible && !mDrawFinished;

                    SurfaceHolder.Callback callbacks[] = null;

                    final boolean surfaceChanged = creating;
                    if (mSurfaceCreated && (surfaceChanged || (!visible && visibleChanged))) {
                        mSurfaceCreated = false;
                        if (mSurface.isValid()) {
                            ... ...
                            callbacks = getSurfaceCallbacks();
                            for (SurfaceHolder.Callback c : callbacks) {
                                c.surfaceDestroyed(mSurfaceHolder);
                            }
                            ... ...
                        }
                    }

                    if (creating) {
                        mSurface.copyFrom(mSurfaceControl);
                    }

                   ... ...
                    if (visible && mSurface.isValid()) {
                        if (!mSurfaceCreated && (surfaceChanged || visibleChanged)) {
                            mSurfaceCreated = true;
                            mIsCreating = true;
                            ... ...
                            if (callbacks == null) {
                                callbacks = getSurfaceCallbacks();
                            }
                            for (SurfaceHolder.Callback c : callbacks) {
                                c.surfaceCreated(mSurfaceHolder);
                            }
                        }
                        if (creating || formatChanged || sizeChanged
                                || visibleChanged || realSizeChanged) {
                           ... ...
                            if (callbacks == null) {
                                callbacks = getSurfaceCallbacks();
                            }
                            for (SurfaceHolder.Callback c : callbacks) {
                                c.surfaceChanged(mSurfaceHolder, mFormat, myWidth, myHeight);
                            }
                        }
                        if (redrawNeeded) {
                           ... ...
                            if (callbacks == null) {
                                callbacks = getSurfaceCallbacks();
                            }

                            mPendingReportDraws++;
                            viewRoot.drawPending();
                            SurfaceCallbackHelper sch =
                                    new SurfaceCallbackHelper(this::onDrawFinished);
                            sch.dispatchSurfaceRedrawNeededAsync(mSurfaceHolder, callbacks);
                        }
                    }
                } finally {
                    mIsCreating = false;
                    if (mSurfaceControl != null && !mSurfaceCreated) {
                        mSurface.release();
                        // If we are not in the stopped state, then the destruction of the Surface
                        // represents a visual change we need to display, and we should go ahead
                        // and destroy the SurfaceControl. However if we are in the stopped state,
                        // we can just leave the Surface around so it can be a part of animations,
                        // and we let the life-time be tied to the parent surface.
                        if (!mWindowStopped) {
                            mSurfaceControl.destroy();
                            mSurfaceControl = null;
                        }
                    }
                }
            } catch (Exception ex) {

            }
            ... ...
        } else {
            // Calculate the window position in case RT loses the window
            // and we need to fallback to a UI-thread driven position update
            getLocationInSurface(mLocation);
          ... ...
        }
    }

在上面的代码中,首先会创建一个SurfaceSession对象,这个对象连接这SurfaceFlinger,SurfaceFlinger服务是系统服务,负责绘制Android应用程序的UI,SurfaceFlinger服务运行在Android系统的System进程中,它负责Android系统的帧缓冲区,Android应用程序为了能够让自己的UI绘制在系统的帧缓冲区上,它们就必须要与SurfaceFlinger服务进行通信。
接下来创建了SurfaceControl对象,我们知道,在创建SurfaceControl对象的时候,会在Native层创建SurfaceControl,然后创建Native的Surface,并返回,保存在java层的SurfaceControl的mNatveObject中。

接下来,调用了mSurfaceControl.setLayer(mSubLayer),我们看SurfaceView的源码,可以知道mSubLayer是APPLICATION_MEDIA_SUBLAYER,表示是媒体窗口。SurfaceView还有一个窗口类型是TYPE_APPLICATION_MEDIA_OVERLAY,它是用来在视频上面显示一个Overlay的,这个Overlay可以用来显示字幕等信息。这两个类型会导致SurfaceView位于它所附加的窗口的下面,即SurfaceView的Z轴位置小于它多附加窗口的Z轴位置。同时,TYPE_APPLICATION_MEDIA_OVERLAY类型的窗口的Z轴比APPLICATION_MEDIA_SUBLAYER类型窗口的Z轴大。

接下来会调用mSurface.copyFrom(mSurfaceControl),将native层的Surface中的内容复制到mSurface。
这样,mSurface就创建完成了。

后面的都是一些回调。

SurfaceView的绘制过程
SurfaceView虽然拥有独立的绘图表面,不过他仍然是宿主窗口的的视图结构中的一个结点,因此,它仍然是可以参与宿主窗口的绘制流程中的,那么,一定会调用它的draw方法:

@Override
    public void draw(Canvas canvas) {
        if (mDrawFinished && !isAboveParent()) {
            // draw() is not called when SKIP_DRAW is set
            if ((mPrivateFlags & PFLAG_SKIP_DRAW) == 0) {
            //如果跳过绘制流程
                // punch a whole in the view-hierarchy below us
                canvas.drawColor(0, PorterDuff.Mode.CLEAR);
            }
        }
        super.draw(canvas);
    }

在上面的方法中的参数canvas是建立在宿主窗口的绘图表面的画布,因此,在这块画布上绘制的任何UI都是出现在宿主窗口的绘图表面上的。

我们可以看到,一般情况下,在这个方法中,会将它所占据的区域绘制为黑色。

那么我们要怎么在SurfaceView上进行UI绘制呢?
在上面分享的例子中,我们可以看到,在另一个线程中调用自定义的draw方法:

private void draw() {
        try {
        	//获得画布
            mCanvas = mHolder.lockCanvas();
            //draw something
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (null != mCanvas) {
            //将已经填充好了UI数据的画布缓冲区提交给SurfaceFlinger服务
                mHolder.unlockCanvasAndPost(mCanvas);
            }
        }
    }

我们可以总结下来:
(1). 在绘图表面的基础上建立一块画布,即获得一个Canvas对象。
(2). 利用Canvas类提供的绘图接口在前面获得的画布上绘制任意的UI。
(3). 将已经填充好了UI数据的画布缓冲区提交给SurfaceFlinger服务,以便SurfaceFlinger服务可以将它合成到屏幕上去。

注意,只有在一个SurfaceView的绘图表面的类型不是SURFACE_TYPE_PUSH_BUFFERS的时候,我们才可以自由地在上面绘制UI。我们使用SurfaceView来显示摄像头预览或者播放视频时,一般就是会将它的绘图表面的类型设置为SURFACE_TYPE_PUSH_BUFFERS。在这种情况下,SurfaceView的绘图表面所使用的图形缓冲区是完全由摄像头服务或者视频播放服务来提供的,因此,我们就不可以随意地去访问该图形缓冲区,而是要由摄像头服务或者视频播放服务来访问,因为该图形缓冲区有可能是在专门的硬件里面分配的。

我们先看SurfaceHolder中的lockCanvas方法:SurfaceHolder是一个Interface,在SurfaceView中创建了匿名实例:

@Override
        public Canvas lockCanvas() {
            return internalLockCanvas(null, false);
        }

 private Canvas internalLockCanvas(Rect dirty, boolean hardware) {
 			//同步机制
            mSurfaceLock.lock();
            ... ...
            Canvas c = null;
            if (!mDrawingStopped && mSurfaceControl != null) {
                try {
                    if (hardware) {
                        c = mSurface.lockHardwareCanvas();
                    } else {
                    //创建图形缓冲区对应的画布
                        c = mSurface.lockCanvas(dirty);
                    }
                } catch (Exception e) {
                    Log.e(LOG_TAG, "Exception locking surface", e);
                }
            }
           ... ...
            if (c != null) {
                mLastLockTime = SystemClock.uptimeMillis();
                return c;
            }
            // If the Surface is not ready to be drawn, then return null,
            // but throttle calls to this function so it isn't called more
            // than every 100ms.
            long now = SystemClock.uptimeMillis();
            long nextTime = mLastLockTime + 100;
            if (nextTime > now) {
                try {
                    Thread.sleep(nextTime-now);
                } catch (InterruptedException e) {
                }
                now = SystemClock.uptimeMillis();
            }
            mLastLockTime = now;
            //解锁
            mSurfaceLock.unlock();
            return null;
        }

可以看到,就是调用了mSurface.lockCanvas方法,创建一块画布。它大致就是通过JNI方法来在当前正在处理的绘图表面上获得一个图形缓冲区,并且将这个图形绘冲区封装在一块类型为Canvas的画布中返回给调用者使用。

调用者在画布上绘制完成所需要的UI之后,就可以将这块画布的图形绘冲区的UI数据提交给SurfaceFlinger服务来处理了,这是通过调用SurfaceHolder类的unlockCanvasAndPost来实现的:

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

可以看到,就是调用了Surface的unlockCanvasAndPost方法,来将绘制好的UI交给SurfaceFlinger服务处理。

由此,我们可以总结出SurfaceView的三个特点:

  1. 具有独立的绘图表面
  2. 需要在宿主窗口上挖一个洞来显示自己
  3. 它的UI绘制可以在单独的线程中进行,这样就可以进行负责的UI绘制,并且不会影响应用程序的主线程响应用户输入。

参考:https://www.kancloud.cn/alex_wsc/androids/473787