SurfaceView源码分析

2,367 阅读8分钟

SurfaceView介绍

SurfaceView是Andoird GUI系统中一种特殊的控件,它可以在非UI线程进行绘图。UI线程的绘制在view绘制流程一篇中介绍过了,本篇将对SurfaceView的绘制进行介绍。在此之前,我们看看SurfaceView的一般用法。

public class SurfaceViewDemo extends SurfaceView
        implements SurfaceHolder.Callback, Runnable {

    private SurfaceHolder mHolder;
    private Canvas mCanvas;
    private boolean bDrawing;

    public SurfaceViewDemo(Context context) {
        super(context);
        initView();
    }

    public SurfaceViewDemo(Context context, AttributeSet attrs) {
        super(context, attrs);
        initView();
    }

    public SurfaceViewDemo(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        initView();
    }

    private void initView() {
        mHolder = getHolder();
        mHolder.addCallback(this);
        setFocusable(true);
        setFocusableInTouchMode(true);
        this.setKeepScreenOn(true);
    }

    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        mIsDrawing = true;
        new Thread(this).start();
    }

    @Override
    public void surfaceChanged(SurfaceHolder holder,
                               int format, int width, int height) {
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        mIsDrawing = false;
    }

    @Override
    public void run() {
        while (bDrawing) {
            draw();
        }
    }

    private void draw() {
        try {
            mCanvas = mHolder.lockCanvas();
            // draw something
        } catch (Exception e) {
        } finally {
            if (mCanvas != null)
                mHolder.unlockCanvasAndPost(mCanvas);
        }
    }
}

SurfaceView的生命周期回调surfaceCreated->SurfaceChanged->surfaceDestroyed。这里在surfaceCreated中进行绘制,这个绘制可以在单独的线程中进行,并不依赖于ui线程。至于为何这样,后面我们再解释。

首先看看surfaceview的源码,先看其成员都有哪些

public class SurfaceView extends View {
    static private final String TAG = "SurfaceView";
    static private final boolean DEBUG = false;

    final ArrayList<SurfaceHolder.Callback> mCallbacks
            = new ArrayList<SurfaceHolder.Callback>();

    final int[] mLocation = new int[2];

    final ReentrantLock mSurfaceLock = new ReentrantLock();
    final Surface mSurface = new Surface();  // Current surface in use  surfaceView使用的绘图表面
    final Surface mNewSurface = new Surface();    // New surface we are switching to
    boolean mDrawingStopped = true;

    final WindowManager.LayoutParams mLayout
            = new WindowManager.LayoutParams();
    IWindowSession mSession;
    MyWindow mWindow;
    final Rect mVisibleInsets = new Rect();
    final Rect mWinFrame = new Rect();
    final Rect mOverscanInsets = new Rect();
    final Rect mContentInsets = new Rect();
    final Configuration mConfiguration = new Configuration();

    static final int KEEP_SCREEN_ON_MSG = 1;
    static final int GET_NEW_SURFACE_MSG = 2;
    static final int UPDATE_WINDOW_MSG = 3;

    int mWindowType = WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA;

    boolean mIsCreating = false;
    ……
}

在SurfaceView中有两个Surface绘图表面,所有绘制操作都是在这个绘图表面上进行的,这里有两个Surface是为了进行绘图时的前后台切换,这样当后台进行绘制时,前台可以显示之前绘制好的Surface表面。IWindowSession是用来和WMS进行会话的session,因为SurfaceView它本质上也是一个Window,它还有一个MyWindow成员,它类似于ViewRootImpl中的W对象,是用来和WMS进行通信交互的。因此它也是一个Binder对象,在WMS一端,它唯一标志了一个窗口对象Window。

SurfaceView的绘制流程

SurfaceView的绘制依然属于view树绘制的一部分,它依赖于宿主窗口,在View树的绘制流程中,我们看看SurfaceView是如何进行自身的绘制的,在performTraversals中绘制view树之前,会通知view树Attach到宿主窗口上,这是通过host.dispatchAttachedToWindow(attachInfo, 0)来实现的,这里的host即DecorView,它是个ViewGroup。在ViewGroup中又会调用子view的dispatchAttachedToWindow方法

//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,
                visibility | (child.mViewFlags & VISIBILITY_MASK));//通知每个子视图他们被添加到宿主窗口上去了
    }
}

//view
void dispatchAttachedToWindow(AttachInfo info, int visibility) {
    mAttachInfo = info;//保存所附加窗口的信息
    if (mOverlay != null) {
        mOverlay.getOverlayView().dispatchAttachedToWindow(info, visibility);
    }
    mWindowAttachCount++;
    // We will need to evaluate the drawable state at least once.
    mPrivateFlags |= PFLAG_DRAWABLE_STATE_DIRTY;
    if (mFloatingTreeObserver != null) {
        info.mTreeObserver.merge(mFloatingTreeObserver);
        mFloatingTreeObserver = null;
    }
    if ((mPrivateFlags&PFLAG_SCROLL_CONTAINER) != 0) {
        mAttachInfo.mScrollContainers.add(this);
        mPrivateFlags |= PFLAG_SCROLL_CONTAINER_ADDED;
    }
    performCollectViewAttributes(mAttachInfo, visibility);
    onAttachedToWindow();//onAttachedToWindow 让子类处理它被附加到宿主窗口时的事件

    ListenerInfo li = mListenerInfo;
    final CopyOnWriteArrayList<OnAttachStateChangeListener> listeners =
            li != null ? li.mOnAttachStateChangeListeners : null;
    if (listeners != null && listeners.size() > 0) {
        for (OnAttachStateChangeListener listener : listeners) {
            listener.onViewAttachedToWindow(this);
        }
    }

    int vis = info.mWindowVisibility;
    if (vis != GONE) {
        onWindowVisibilityChanged(vis);//通知宿主窗口的可见性发生了变化
    }
    if ((mPrivateFlags&PFLAG_DRAWABLE_STATE_DIRTY) != 0) {
        // If nobody has evaluated the drawable state yet, then do it now.
        refreshDrawableState();
    }
    needGlobalAttributesUpdate(false);
}

对于子View,它的dispatchAttachedToWindow会回调onAttachedToWindow通知view被添加到宿主window上,随后宿主窗口可见,还会通过onWindowVisibilityChanged通知可见性发生了变化。这里我们看看SurfaceView它是如何做处理的。

//surfaceView被添加到宿主窗口时回调
@Override
protected void onAttachedToWindow() {
    super.onAttachedToWindow();
    mParent.requestTransparentRegion(this);//请求父窗口设置一块透明区域  即 在父窗口视图上挖一个洞  mParent为其父类View
    mSession = getWindowSession();//获取IWindowSession 这是一个binder负责和WMS通信  后续通过这个session来向WMS请求绘图表面
    mLayout.token = getWindowToken();//这个token也是ViewRootImpl中的W
    mLayout.setTitle("SurfaceView");
    mViewVisibility = getVisibility() == VISIBLE;

    if (!mGlobalListenersAdded) {
        ViewTreeObserver observer = getViewTreeObserver();
        observer.addOnScrollChangedListener(mScrollChangedListener);
        observer.addOnPreDrawListener(mDrawListener);
        mGlobalListenersAdded = true;
    }
}

在SurfaceView的onAttachedToWindow中,SurfaceView会向父窗口请求设置一块透明区域,这个透明区域是为了使SurfaceView在宿主窗口中可见,因为SurfaceView被添加时它的Z序是小于宿主窗口的,即它是显示在宿主窗口下面的,要显示SurfaceView就需要在宿主窗口设置对应大小的透明区域。同时还会取到IWindowSession 用来和WMS进行通信。

 @Override
protected void onWindowVisibilityChanged(int visibility) {//宿主窗口的可见性发生了变化
    super.onWindowVisibilityChanged(visibility);
    //mWindowVisibility表示SurfaceView的宿主窗口的可见性,mViewVisibility表示SurfaceView自身的可见性。
        //只有当mWindowVisibility和mViewVisibility的值均等于true的时候,mRequestedVisible的值才为true,表示SurfaceView是可见的
    mWindowVisibility = visibility == VISIBLE;
    mRequestedVisible = mWindowVisibility && mViewVisibility;
    updateWindow(false, false);
}

关于可见性的变化,SurfaceView计算当前宿主窗口的可见性,并通过updateWindow来更新自身的窗口。

private void updateWindow(boolean force, boolean redrawNeeded) {
    ……
    getLocationInWindow(mLocation);
    final boolean creating = mWindow == null;
    final boolean formatChanged = mFormat != mRequestedFormat;
    final boolean sizeChanged = mWidth != myWidth || mHeight != myHeight;
    final boolean visibleChanged = mVisible != mRequestedVisible;

    if (force || creating || formatChanged || sizeChanged || visibleChanged
        || mLeft != mLocation[0] || mTop != mLocation[1]
        || mUpdateWindowNeeded || mReportDrawNeeded || redrawNeeded) {

        try {
            final boolean visible = mVisible = mRequestedVisible;
            mLeft = mLocation[0];
            mTop = mLocation[1];
            mWidth = myWidth;
            mHeight = myHeight;
            mFormat = mRequestedFormat;

            // Places the window relative
            mLayout.x = mLeft;
            mLayout.y = mTop;
            mLayout.width = getWidth();
            mLayout.height = getHeight();
            if (mTranslator != null) {
                mTranslator.translateLayoutParamsInAppWindowToScreen(mLayout);
            }

            mLayout.format = mRequestedFormat;
            mLayout.flags |=WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
                            | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                            | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS
                            | WindowManager.LayoutParams.FLAG_SCALED
                            | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                            | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
            if (!getContext().getResources().getCompatibilityInfo().supportsScreen()) {
                mLayout.privateFlags |=
                    WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW;
            }
            mLayout.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;

            if (mWindow == null) {
                Display display = getDisplay();
                mWindow = new MyWindow(this);
                mLayout.type = mWindowType;
                mLayout.gravity = Gravity.START|Gravity.TOP;
                mSession.addToDisplayWithoutInputChannel(mWindow, mWindow.mSeq, mLayout,
                        mVisible ? VISIBLE : GONE, display.getDisplayId(), mContentInsets);
                //为surfaceview添加窗口,对应于WMS一端
            }

            boolean realSizeChanged;
            boolean reportDrawNeeded;

            int relayoutResult;

            mSurfaceLock.lock();
            try {
                mUpdateWindowNeeded = false;
                reportDrawNeeded = mReportDrawNeeded;
                mReportDrawNeeded = false;
                mDrawingStopped = !visible;

                relayoutResult = mSession.relayout(
                    mWindow, mWindow.mSeq, mLayout, mWidth, mHeight,
                        visible ? VISIBLE : GONE,
                        WindowManagerGlobal.RELAYOUT_DEFER_SURFACE_DESTROY,
                        mWinFrame, mOverscanInsets, mContentInsets,
                        mVisibleInsets, mConfiguration, mNewSurface);//计算窗口大小,分配绘图表面
                if ((relayoutResult & WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME) != 0) {
                    mReportDrawNeeded = true;
                }

                mSurfaceFrame.left = 0;
                mSurfaceFrame.top = 0;
                if (mTranslator == null) {
                    mSurfaceFrame.right = mWinFrame.width();
                    mSurfaceFrame.bottom = mWinFrame.height();
                } else {
                    float appInvertedScale = mTranslator.applicationInvertedScale;
                    mSurfaceFrame.right = (int) (mWinFrame.width() * appInvertedScale + 0.5f);
                    mSurfaceFrame.bottom = (int) (mWinFrame.height() * appInvertedScale + 0.5f);
                }

                final int surfaceWidth = mSurfaceFrame.right;
                final int surfaceHeight = mSurfaceFrame.bottom;
                realSizeChanged = mLastSurfaceWidth != surfaceWidth
                        || mLastSurfaceHeight != surfaceHeight;
                mLastSurfaceWidth = surfaceWidth;
                mLastSurfaceHeight = surfaceHeight;
            } finally {
                mSurfaceLock.unlock();
            }

            ……
            if (visible && mSurface.isValid()) {
                if (!mSurfaceCreated && (surfaceChanged || visibleChanged)) {
                    mSurfaceCreated = true;
                    mIsCreating = true;
                    if (DEBUG) Log.i(TAG, "visibleChanged -- surfaceCreated");
                    if (callbacks == null) {
                        callbacks = getSurfaceCallbacks();
                    }
                    for (SurfaceHolder.Callback c : callbacks) {
                        c.surfaceCreated(mSurfaceHolder);
                    }
                }
                if (creating || formatChanged || sizeChanged
                        || visibleChanged || realSizeChanged) {
                    if (DEBUG) Log.i(TAG, "surfaceChanged -- format=" + mFormat
                            + " w=" + myWidth + " h=" + myHeight);
                    if (callbacks == null) {
                        callbacks = getSurfaceCallbacks();
                    }
                    for (SurfaceHolder.Callback c : callbacks) {
                        c.surfaceChanged(mSurfaceHolder, mFormat, myWidth, myHeight);
                    }
                }
                if (redrawNeeded) {
                    if (DEBUG) Log.i(TAG, "surfaceRedrawNeeded");
                    if (callbacks == null) {
                        callbacks = getSurfaceCallbacks();
                    }
                    for (SurfaceHolder.Callback c : callbacks) {
                        if (c instanceof SurfaceHolder.Callback2) {
                            ((SurfaceHolder.Callback2)c).surfaceRedrawNeeded(
                                    mSurfaceHolder);
                        }
                    }
                }
            }
            ……
        } catch (RemoteException ex) {}
    }
}

对于SurfaceView来说,它拥有独立的Window,这个Window同样受WMS的管理,因此在绘制时需要将该Window添加到WMS中去,同时,它也有它自己的绘图表面,因此在绘制之前需要计算它自身的窗口大小并向WMS请求绘图表面。关于分配绘图缓冲在其它章节已做了描述,本篇不再多做介绍。

SurfaceView的挖洞过程

SurfaceView的窗口类型一般都是TYPE_APPLICATION_MEDIA或者TYPE_APPLICATION_MEDIA_OVERLAY,它在添加到宿主窗口上时,会在父窗口的表面设置一个透明区域以显示SurfaceView自身的内容,这是通过requestTransparentRegion来进行的,下面我们就看看这个过程是如何完成的。

//ViewGroup
public void requestTransparentRegion(View child) {
    if (child != null) {
        child.mPrivateFlags |= View.PFLAG_REQUEST_TRANSPARENT_REGIONS;
        if (mParent != null) {
            mParent.requestTransparentRegion(this);
        }
    }
}

//ViewRootImpl    
@Override
public void requestTransparentRegion(View child) {
    // the test below should not fail unless someone is messing with us
    checkThread();
    if (mView == child) {
        mView.mPrivateFlags |= View.PFLAG_REQUEST_TRANSPARENT_REGIONS;
        // Need to make sure we re-evaluate the window attributes next
        // time around, to ensure the window has the correct format.
        mWindowAttributesChanged = true;
        mWindowAttributesChangesFlag = 0;
        requestLayout();
    }
}

SurfaceView请求透明区域的过程实际上为父View打上PFLAG_REQUEST_TRANSPARENT_REGIONS标记,这表示它要在宿主窗口上设置透明区域,这个过程直到ViewRootImpl,ViewRootImpl它也是ViewParent,它将调用requestLayout触发performTraversals来请求窗口重新布局和绘制,从而收集透明区域,最后通过WMS为该宿主窗口设置一个总的透明区域。

private void performTraversals() {
    ……
    if ((host.mPrivateFlags & View.PFLAG_REQUEST_TRANSPARENT_REGIONS) != 0) {
        host.getLocationInWindow(mTmpLocation);
        mTransparentRegion.set(mTmpLocation[0], mTmpLocation[1],
                mTmpLocation[0] + host.mRight - host.mLeft,
                mTmpLocation[1] + host.mBottom - host.mTop);

        host.gatherTransparentRegion(mTransparentRegion);
        if (mTranslator != null) {
            mTranslator.translateRegionInWindowToScreen(mTransparentRegion);
        }

        if (!mTransparentRegion.equals(mPreviousTransparentRegion)) {
            mPreviousTransparentRegion.set(mTransparentRegion);
            mFullRedrawNeeded = true;
            // reconfigure window manager
            try {
                mWindowSession.setTransparentRegion(mWindow, mTransparentRegion);
            } catch (RemoteException e) {
            }
        }
    }
    ……
}

收集透明区域也是从DecorView开始的,它调用gatherTransparentRegion来完成,这个区域是由mTransparentRegion来存储维护的。如果透明区域发生了变化这时候就需要重新设置该透明区域到WMS中去。

//ViewGroup.java
@Override
public boolean gatherTransparentRegion(Region region) {
    // If no transparent regions requested, we are always opaque.
    final boolean meOpaque = (mPrivateFlags & View.PFLAG_REQUEST_TRANSPARENT_REGIONS) == 0;
    if (meOpaque && region == null) {
        // The caller doesn't care about the region, so stop now.
        return true;
    }
    super.gatherTransparentRegion(region);
    final View[] children = mChildren;
    final int count = mChildrenCount;
    boolean noneOfTheChildrenAreTransparent = true;
    for (int i = 0; i < count; i++) {
        final View child = children[i];
        if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
            if (!child.gatherTransparentRegion(region)) {
                noneOfTheChildrenAreTransparent = false;
            }
        }
    }
    return meOpaque || noneOfTheChildrenAreTransparent;
}

收集透明区域会遍历父view下的所有子view的透明区域。在开始收集之前,首先将透明区域设置为DecorView视图的大小,然后遍历子view,如果view是不透明的区域则将其从初始的透明区域中移除,这样最后保留下来的就是需要设置的透明区域。

下面我们看看SurfaceView它是如何进行计算它的透明区域的。

@Override
public boolean gatherTransparentRegion(Region region) {
    if (mWindowType == WindowManager.LayoutParams.TYPE_APPLICATION_PANEL) {
        return super.gatherTransparentRegion(region);
    }

    boolean opaque = true;
    if ((mPrivateFlags & PFLAG_SKIP_DRAW) == 0) {
        // this view draws, remove it from the transparent region
        opaque = super.gatherTransparentRegion(region);
    } else if (region != null) {
        int w = getWidth();
        int h = getHeight();
        if (w>0 && h>0) {
            getLocationInWindow(mLocation);
            // otherwise, punch a hole in the whole hierarchy
            int l = mLocation[0];
            int t = mLocation[1];
            region.op(l, t, l+w, t+h, Region.Op.UNION);
        }
    }
    if (PixelFormat.formatHasAlpha(mRequestedFormat)) {
        opaque = false;
    }
    return opaque;
}

SurfaceView首先判断窗口类型是否作为WindowManager.LayoutParams.TYPE_APPLICATION_PANEL,如果是则表示SurfaceView是用来作为应用面板的,这时候调用父类view的gatherTransparentRegion来处理,如果需要绘制,则将view的绘制区域从从参数region所描述的透明区域中移除。

SurfaceView的绘制

SurfaceView具有独立的绘图Surface,但它仍然属于View树中的子节点,它依附在宿主窗口上,所以它自身的view也是需要绘制到宿主窗口的Surface上的。在介绍view绘制流程一节中我们知道了view的绘制会触发draw和dispatchDraw方法,前者绘制它自身,后者负责绘制子view。

//SurfaceView.java
@Override
public void draw(Canvas canvas) {
    if (mWindowType != WindowManager.LayoutParams.TYPE_APPLICATION_PANEL) {
        // 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);
}

@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 & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
            // punch a whole in the view-hierarchy below us
            canvas.drawColor(0, PorterDuff.Mode.CLEAR);
        }
    }
    super.dispatchDraw(canvas);
}

draw和dispatchDraw方法的参数canvas代表的是宿主窗口的绘图表面的画布,这表示surfaceview自身的ui是绘制在 宿主绘图表面上的,如果mWindowType不为WindowManager.LayoutParams.TYPE_APPLICATION_PANEL表示SurfaceView不作为应用面板,那么将其view所占区域设置为黑色。

除了在宿主窗口上绘制ui,SurfaceView可以在自身的绘图表面上绘制内容,一般的绘制流程如下:

 SurfaceView sv = (SurfaceView )findViewById(R.id.surface_view);    
 SurfaceHolder sh = sv.getHolder();  
 Cavas canvas = sh.lockCanvas()  
  
 //Draw something on canvas 
 ......  
  
 sh.unlockCanvasAndPost(canvas);  

需要注意的是,当绘图表面的类型设置为SURFACE_TYPE_PUSH_BUFFERS时,表示绘图表面的缓冲区不受我们控制,它是由摄像头或者视频播放服务来提供的。因此不能在随意在上面进行内容绘制。

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

private final Canvas internalLockCanvas(Rect dirty) {
    mSurfaceLock.lock();
    Canvas c = null;
    if (!mDrawingStopped && mWindow != null) {
        try {
            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;
}

SurfaceView的lockCanvas是从其绘图表面申请缓冲区,也就是Canvas,应用上层可以使用这个Canvas来进行内容的绘制,需要注意的是这个画布并不是线程安全的,需要通过mSurfaceLock的锁保护。