SurfaceView源碼分析(二):SurfaceView的"挖洞"過(guò)程

上一篇文章講了SurfaceView創(chuàng)建Surface的過(guò)程,接下來(lái)我們來(lái)看下SurfaceView是如何"挖洞"的。說(shuō)起"挖洞",本質(zhì)上其實(shí)就是設(shè)置一塊區(qū)域,在最后繪制的時(shí)候不要對(duì)這塊區(qū)域進(jìn)行繪制即可


不過(guò)在講"挖洞"之前,我們首先來(lái)思考一個(gè)問(wèn)題:為什么要"挖洞"呢?我們先來(lái)看下面這段代碼:

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

還是在SurfaceView的updateSurface方法里。mSubLayer默認(rèn)是APPLICATION_MEDIA_SUBLAYER, mSurfaceControl.setLayer(mSubLayer)就是講mSubLayer傳給SurfaceFlinger。如果碰到有兩個(gè)SurfaceView的時(shí)候我們通常會(huì)讓其中一個(gè)需要顯示在上面的surfaceView調(diào)用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會(huì)處于當(dāng)前宿主窗口的下方。因此才需要將SurfaceView所對(duì)應(yīng)的那塊區(qū)域設(shè)置成透明才能讓SurfaceView顯示出來(lái)。


"挖洞"首先還是從ViewRootImpl#performTraversals開始說(shuō)起:

    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在初始化的時(shí)候是true,只有第一次調(diào)用performTravesals的時(shí)候才會(huì)執(zhí)行里面的代碼,執(zhí)行一次以后,mFirst就會(huì)設(shè)置為false。
這段代碼主要看host.dispatchAttachedToWindow(mAttachInfo, 0);這個(gè)host我們?cè)谏弦黄仓v到過(guò)就是DecorView。而DecorView是繼承ViewGroup的,另外本身并沒(méi)有重寫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()));
        }
    }

這里面主要就是調(diào)用了View的dispatchAttachedToWindow,而View的dispatchAttachedToWindow里面又會(huì)調(diào)用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;
        }
    }

我們看到這里面執(zhí)行了 mParent.requestTransparentRegion(SurfaceView.this);這么一句代碼,這句代碼看名字應(yīng)該就是請(qǐng)求父View測(cè)量一下當(dāng)前SurfaceView的位置大小。那就繼續(xù)看下去來(lái)印證下是否是這樣子的,mParent就是View的父View即對(duì)應(yīng)的ViewGroup,

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

ViewGroup里面依然會(huì)調(diào)用mParent.requestTransparentRegion方法,那么最終肯定會(huì)到頂層的ViewGroup也就是DecorView,它的mParent便是ViewRootImpl,所以重新進(jìn)入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,相當(dāng)于給mPrivateFlags設(shè)置了PFLAG_REQUEST_TRANSPARENT_REGIONS的屬性
  2. 第二件事就是requestLayout,這個(gè)方法最終會(huì)重新調(diào)用ViewRootImpl的performTraversals方法??磥?lái)"挖洞"過(guò)程估計(jì)也是在這個(gè)方法里面了。
    private void performTraversals() {       
        ...代碼省略...
        //當(dāng)執(zhí)行RequestLayout的時(shí)候,layoutRequested參數(shù)為true,由于當(dāng)前窗口沒(méi)有被關(guān)閉,因此mStopped必然是false,所以didLayout是true
        final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw);
        boolean triggerGlobalLayoutListener = didLayout
                || mAttachInfo.mRecomputeGlobalAttributes;
        if (didLayout) {
            //這里會(huì)執(zhí)行ViewGroup的onLayout方法
            performLayout(lp, mWidth, mHeight);

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

            //對(duì)應(yīng)了之前的requestTransparentRegion方法,將PFLAG_REQUEST_TRANSPARENT_REGIONS賦值給了mPrivateFlags,所以此處條件會(huì)進(jìn)入。
            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條件滿足,所以會(huì)進(jìn)入里面,然后調(diào)用host.gatherTransparentRegion。host就是DecorView對(duì)象。

    @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會(huì)調(diào)用父類ViewGroup的gatherTransparentRegion。如果沒(méi)猜錯(cuò),ViewGroup應(yīng)該不會(huì)做多少處理直接分發(fā)給對(duì)應(yīng)的子View做相應(yīng)的處理:

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

確實(shí)ViewGroup里面就是做了Child View的遍歷,然后對(duì)每個(gè)View做gatherTransparentRegion處理,然后計(jì)算出對(duì)應(yīng)需要透明的區(qū)域。本文主角是SurfaceView,所以直接關(guān)注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的寬高,然后計(jì)算除了SurfaceView在屏幕上的具體位置,然后對(duì)region進(jìn)行重新賦值。

所以gatherTransparentRegion主要是為了計(jì)算出需要設(shè)置透明區(qū)域的范圍。后續(xù)我們需要告訴SurfaceFlinger這塊透明區(qū)域的具體位置。那么我們?cè)俅位氐絍iewRootImpl去。

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

我們看到在剛才調(diào)用gatherTransparentRegion方法的條件里面,有上面這段代碼,當(dāng)當(dāng)前需要設(shè)置的透明區(qū)域不跟之前的相同時(shí),通過(guò)mWindowSession的setTransparentRegion方法進(jìn)行設(shè)置。mWindowSession是一個(gè)IWindowSession接口。Session類實(shí)現(xiàn)了IWindowSession,是一個(gè)遠(yuǎn)程的進(jìn)程,通過(guò)Binder進(jìn)行通訊

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

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

Session里面沒(méi)有做特殊的處理,直接交給了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);
        }
    }

然后調(diào)用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");
        }
    }
}

最終調(diào)用了 mSurfaceControl.setTransparentRegionHint(region);,這個(gè)mSurfaceControl就是SurfaceControlWithBackground,是不是覺(jué)得有點(diǎn)熟悉?沒(méi)錯(cuò),就是上一篇在創(chuàng)建Surface的時(shí)候也是這個(gè)類在操作。

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

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

而SurfaceControlWithBackground則是調(diào)用了mBackgroundControl.setTransparentRegionHint(region);,之后會(huì)調(diào)用nativeSetTransparentRegionHint方法,看這名字就是要進(jìn)入C++層了,那我們就進(jìn)去探探究竟吧。

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對(duì)象,然后調(diào)用了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。下面給出對(duì)應(yīng)的時(shí)序圖:


SurfaceView挖洞過(guò)程.jpg

至此SurfaceView的"挖洞"過(guò)程結(jié)束,那么下一篇就開始講SurfaceView的繪制的過(guò)程了。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容