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

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


不過在講"挖洞"之前,我們首先來思考一個問題:為什么要"挖洞"呢?我們先來看下面這段代碼:

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

還是在SurfaceView的updateSurface方法里。mSubLayer默認是APPLICATION_MEDIA_SUBLAYER, mSurfaceControl.setLayer(mSubLayer)就是講mSubLayer傳給SurfaceFlinger。如果碰到有兩個SurfaceView的時候我們通常會讓其中一個需要顯示在上面的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會處于當前宿主窗口的下方。因此才需要將SurfaceView所對應(yīng)的那塊區(qū)域設(shè)置成透明才能讓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,只有第一次調(diào)用performTravesals的時候才會執(zhí)行里面的代碼,執(zhí)行一次以后,mFirst就會設(shè)置為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()));
        }
    }

這里面主要就是調(diào)用了View的dispatchAttachedToWindow,而View的dispatchAttachedToWindow里面又會調(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)該就是請求父View測量一下當前SurfaceView的位置大小。那就繼續(xù)看下去來印證下是否是這樣子的,mParent就是View的父View即對應(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里面依然會調(diào)用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設(shè)置了PFLAG_REQUEST_TRANSPARENT_REGIONS的屬性
  2. 第二件事就是requestLayout,這個方法最終會重新調(diào)用ViewRootImpl的performTraversals方法??磥?挖洞"過程估計也是在這個方法里面了。
    private void performTraversals() {       
        ...代碼省略...
        //當執(zhí)行RequestLayout的時候,layoutRequested參數(shù)為true,由于當前窗口沒有被關(guān)閉,因此mStopped必然是false,所以didLayout是true
        final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw);
        boolean triggerGlobalLayoutListener = didLayout
                || mAttachInfo.mRecomputeGlobalAttributes;
        if (didLayout) {
            //這里會執(zhí)行ViewGroup的onLayout方法
            performLayout(lp, mWidth, mHeight);

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

            //對應(yīng)了之前的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條件滿足,所以會進入里面,然后調(diào)用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會調(diào)用父類ViewGroup的gatherTransparentRegion。如果沒猜錯,ViewGroup應(yīng)該不會做多少處理直接分發(fā)給對應(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;
    }

確實ViewGroup里面就是做了Child View的遍歷,然后對每個View做gatherTransparentRegion處理,然后計算出對應(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的寬高,然后計算除了SurfaceView在屏幕上的具體位置,然后對region進行重新賦值。

所以gatherTransparentRegion主要是為了計算出需要設(shè)置透明區(qū)域的范圍。后續(xù)我們需要告訴SurfaceFlinger這塊透明區(qū)域的具體位置。那么我們再次回到ViewRootImpl去。

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

我們看到在剛才調(diào)用gatherTransparentRegion方法的條件里面,有上面這段代碼,當當前需要設(shè)置的透明區(qū)域不跟之前的相同時,通過mWindowSession的setTransparentRegion方法進行設(shè)置。mWindowSession是一個IWindowSession接口。Session類實現(xiàn)了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);
        }
    }

然后調(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);,這個mSurfaceControl就是SurfaceControlWithBackground,是不是覺得有點熟悉?沒錯,就是上一篇在創(chuàng)建Surface的時候也是這個類在操作。

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

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

而SurfaceControlWithBackground則是調(diào)用了mBackgroundControl.setTransparentRegionHint(region);,之后會調(diào)用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對象,然后調(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。下面給出對應(yīng)的時序圖:


SurfaceView挖洞過程.jpg

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

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

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