SurfaceView源码分析(二):SurfaceView的"挖洞"过程

上一篇文章讲了SurfaceView创建Surface的过程,接下来我们来看下SurfaceView是如何"挖洞"的。说起"挖洞",本质上其实就是设置一块区域,在最后绘制的时候不要对这块区域进行绘制即可


不过在讲"挖洞"之前,我们首先来思考一个问题:为什么要"挖洞"呢?我们先来看下面这段代码:

protected void updateSurface() {
      ...代码省略...
      mSurfaceControl.setLayer(mSubLayer);
      ...代码省略...
}

还是在SurfaceView的updateSurface方法里。mSubLayer默认是APPLICATION_MEDIA_SUBLAYER, mSurfaceControl.setLayer(mSubLayer)就是讲mSubLayer传给SurfaceFlinger。如果碰到有两个SurfaceView的时候我们通常会让其中一个需要显示在上面的surfaceView调用setZOrderMediaOverlay(true)。

    public void setZOrderMediaOverlay(boolean isMediaOverlay) {
        mSubLayer = isMediaOverlay
            ? APPLICATION_MEDIA_OVERLAY_SUBLAYER : APPLICATION_MEDIA_SUBLAYER;
    }

APPLICATION_MEDIA_OVERLAY_SUBLAYER值是-1,APPLICATION_MEDIA_SUBLAYER的值是-2,mSubLayer的值越大,就越显示在上面。所以SurfaceView会处于当前宿主窗口的下方。因此才需要将SurfaceView所对应的那块区域设置成透明才能让SurfaceView显示出来。


"挖洞"首先还是从ViewRootImpl#performTraversals开始说起:

    private void performTraversals() {
        ...代码省略...
       if (mFirst) {
            ...代码省略...
            host.dispatchAttachedToWindow(mAttachInfo, 0);
            mAttachInfo.mTreeObserver.dispatchOnWindowAttachedChange(true);
            dispatchApplyInsets(host);
            //Log.i(mTag, "Screen on initialized: " + attachInfo.mKeepScreenOn);

        }

        ...代码省略...
        mFirst = false;
        ...代码省略....
    }

mFirst在初始化的时候是true,只有第一次调用performTravesals的时候才会执行里面的代码,执行一次以后,mFirst就会设置为false。
这段代码主要看host.dispatchAttachedToWindow(mAttachInfo, 0);这个host我们在上一篇也讲到过就是DecorView。而DecorView是继承ViewGroup的,另外本身并没有重写dispatchAttachedToWindow方法,所以我们直接看ViewGroup的#dispatchAttachedToWindow方法即可:


    @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,而View的dispatchAttachedToWindow里面又会调用onAttachToWindow方法,而我们本篇的主角是SurfaceView,所以这里就直接给出SurfaceView的onAttachToWindow方法:

   @Override
    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;
        }
    }

我们看到这里面执行了 mParent.requestTransparentRegion(SurfaceView.this);这么一句代码,这句代码看名字应该就是请求父View测量一下当前SurfaceView的位置大小。那就继续看下去来印证下是否是这样子的,mParent就是View的父View即对应的ViewGroup,

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

ViewGroup里面依然会调用mParent.requestTransparentRegion方法,那么最终肯定会到顶层的ViewGroup也就是DecorView,它的mParent便是ViewRootImpl,所以重新进入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();
        }
    }

ViewRootImpl做了两件事:

  1. 将mView的mPrivateFlags逻辑或上View.PFLAG_REQUEST_TRANSPARENT_REGIONS,相当于给mPrivateFlags设置了PFLAG_REQUEST_TRANSPARENT_REGIONS的属性
  2. 第二件事就是requestLayout,这个方法最终会重新调用ViewRootImpl的performTraversals方法。看来"挖洞"过程估计也是在这个方法里面了。
    private void performTraversals() {       
        ...代码省略...
        //当执行RequestLayout的时候,layoutRequested参数为true,由于当前窗口没有被关闭,因此mStopped必然是false,所以didLayout是true
        final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw);
        boolean triggerGlobalLayoutListener = didLayout
                || mAttachInfo.mRecomputeGlobalAttributes;
        if (didLayout) {
            //这里会执行ViewGroup的onLayout方法
            performLayout(lp, mWidth, mHeight);

            // By this point all views have been sized and positioned
            // We can compute the transparent area

            //对应了之前的requestTransparentRegion方法,将PFLAG_REQUEST_TRANSPARENT_REGIONS赋值给了mPrivateFlags,所以此处条件会进入。
            if ((host.mPrivateFlags & View.PFLAG_REQUEST_TRANSPARENT_REGIONS) != 0) {
                // start out transparent
                // TODO: AVOID THAT CALL BY CACHING THE RESULT?
                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) {
                    }
                }
            }

            if (DBG) {
                System.out.println("======================================");
                System.out.println("performTraversals -- after setFrame");
                host.debug();
            }
        }
        ...代码省略...
    }

这里(host.mPrivateFlags & View.PFLAG_REQUEST_TRANSPARENT_REGIONS) != 0条件满足,所以会进入里面,然后调用host.gatherTransparentRegion。host就是DecorView对象。

    @Override
    public boolean gatherTransparentRegion(Region region) {
        boolean statusOpaque = gatherTransparentRegion(mStatusColorViewState, region);
        boolean navOpaque = gatherTransparentRegion(mNavigationColorViewState, region);
        boolean decorOpaque = super.gatherTransparentRegion(region);

        // combine bools after computation, so each method above always executes
        return statusOpaque || navOpaque || decorOpaque;
    }

DecorView会调用父类ViewGroup的gatherTransparentRegion。如果没猜错,ViewGroup应该不会做多少处理直接分发给对应的子View做相应的处理:

    @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);
        // Instead of naively traversing the view tree, we have to traverse according to the Z
        // order here. We need to go with the same order as dispatchDraw().
        // One example is that after surfaceView punch a hole, we will still allow other views drawn
        // on top of that hole. In this case, those other views should be able to cut the
        // transparent region into smaller area.
        final int childrenCount = mChildrenCount;
        boolean noneOfTheChildrenAreTransparent = true;
        if (childrenCount > 0) {
            final ArrayList preorderedList = buildOrderedChildList();
            final boolean customOrder = preorderedList == null
                    && isChildrenDrawingOrderEnabled();
            final View[] children = mChildren;
            for (int i = 0; i < childrenCount; i++) {
                final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);
                final View child = getAndVerifyPreorderedView(preorderedList, children, childIndex);
                if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
                    if (!child.gatherTransparentRegion(region)) {
                        noneOfTheChildrenAreTransparent = false;
                    }
                }
            }
            if (preorderedList != null) preorderedList.clear();
        }
        return meOpaque || noneOfTheChildrenAreTransparent;
    }

确实ViewGroup里面就是做了Child View的遍历,然后对每个View做gatherTransparentRegion处理,然后计算出对应需要透明的区域。本文主角是SurfaceView,所以直接关注SurfaceView的gatherTransparentRegion即可:

    @Override
    public boolean gatherTransparentRegion(Region region) {
        if (isAboveParent() || !mDrawFinished) {
            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的宽高,然后计算除了SurfaceView在屏幕上的具体位置,然后对region进行重新赋值。

所以gatherTransparentRegion主要是为了计算出需要设置透明区域的范围。后续我们需要告诉SurfaceFlinger这块透明区域的具体位置。那么我们再次回到ViewRootImpl去。

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

我们看到在刚才调用gatherTransparentRegion方法的条件里面,有上面这段代码,当当前需要设置的透明区域不跟之前的相同时,通过mWindowSession的setTransparentRegion方法进行设置。mWindowSession是一个IWindowSession接口。Session类实现了IWindowSession,是一个远程的进程,通过Binder进行通讯

public class Session extends IWindowSession.Stub
        implements IBinder.DeathRecipient {

          @Override
    public void setTransparentRegion(IWindow window, Region region) {
        mService.setTransparentRegionWindow(this, window, region);
    }
}

Session里面没有做特殊的处理,直接交给了mService处理,此处的mService 便是WindowManagerService。

    void setTransparentRegionWindow(Session session, IWindow client, Region region) {
        long origId = Binder.clearCallingIdentity();
        try {
            synchronized (mWindowMap) {
                WindowState w = windowForClientLocked(session, client, false);
                if (SHOW_TRANSACTIONS) WindowManagerService.logSurface(w,
                        "transparentRegionHint=" + region, false);

                if ((w != null) && w.mHasSurface) {
                    w.mWinAnimator.setTransparentRegionHintLocked(region);
                }
            }
        } finally {
            Binder.restoreCallingIdentity(origId);
        }
    }

然后调用WindowStateAnimator的setTransparentRegionHintLocked方法

 class WindowStateAnimator {   
    void setTransparentRegionHintLocked(final Region region) {
        if (mSurfaceController == null) {
            Slog.w(TAG, "setTransparentRegionHint: null mSurface after mHasSurface true");
            return;
        }
        mSurfaceController.setTransparentRegionHint(region);
    }
}
 class WindowSurfaceController {  
    void setTransparentRegionHint(final Region region) {
        if (mSurfaceControl == null) {
            Slog.w(TAG, "setTransparentRegionHint: null mSurface after mHasSurface true");
            return;
        }
        if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG, ">>> OPEN TRANSACTION setTransparentRegion");
        mService.openSurfaceTransaction();
        try {
            mSurfaceControl.setTransparentRegionHint(region);
        } finally {
            mService.closeSurfaceTransaction();
            if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG,
                    "<<< CLOSE TRANSACTION setTransparentRegion");
        }
    }
}

最终调用了 mSurfaceControl.setTransparentRegionHint(region);,这个mSurfaceControl就是SurfaceControlWithBackground,是不是觉得有点熟悉?没错,就是上一篇在创建Surface的时候也是这个类在操作。

    @Override
    public void setTransparentRegionHint(Region region) {
        super.setTransparentRegionHint(region);

        if (mBackgroundControl == null) {
            return;
        }
        mBackgroundControl.setTransparentRegionHint(region);
    }

而SurfaceControlWithBackground则是调用了mBackgroundControl.setTransparentRegionHint(region);,之后会调用nativeSetTransparentRegionHint方法,看这名字就是要进入C++层了,那我们就进去探探究竟吧。

frameworks/base/core/jni/android_view_SurfaceControl.cpp

static void nativeSetTransparentRegionHint(JNIEnv* env, jclass clazz, jlong transactionObj,
        jlong nativeObject, jobject regionObj) {
    ...代码省略...

    {
        auto transaction = reinterpret_cast(transactionObj);
        transaction->setTransparentRegionHint(ctrl, reg);
    }
}

这里获取了SurfaceComposerClient对象,然后调用了SurfaceComposerClient的setTransparentRegionHint方法。

frameworks/native/libs/gui/SurfaceComposerClient.cpp

SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setTransparentRegionHint(
        const sp& sc,
        const Region& transparentRegion) {
    layer_state_t* s = getLayerState(sc);
    if (!s) {
        mStatus = BAD_INDEX;
        return *this;
    }
    s->what |= layer_state_t::eTransparentRegionChanged;
    s->transparentRegion = transparentRegion;
    return *this;
}

最终赋值给了SurfaceFlinger。下面给出对应的时序图:


SurfaceView挖洞过程.jpg

至此SurfaceView的"挖洞"过程结束,那么下一篇就开始讲SurfaceView的绘制的过程了。

你可能感兴趣的:(SurfaceView源码分析(二):SurfaceView的"挖洞"过程)