04 SurfaceView-源码分析-"挖洞"

作者: 凤邪摩羯 | 来源:发表于2021-07-16 09:13 被阅读0次

    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<View> 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<SurfaceComposerClient::Transaction*>(transactionObj);
            transaction->setTransparentRegionHint(ctrl, reg);
        }
    }
    
    

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

    frameworks/native/libs/gui/SurfaceComposerClient.cpp

    SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setTransparentRegionHint(
            const sp<SurfaceControl>& 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。下面给出对应的时序图:

    image

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

    相关文章

      网友评论

        本文标题:04 SurfaceView-源码分析-"挖洞"

        本文链接:https://www.haomeiwen.com/subject/fcgmaktx.html