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做了两件事:
- 将mView的mPrivateFlags逻辑或上View.PFLAG_REQUEST_TRANSPARENT_REGIONS,相当于给mPrivateFlags设置了PFLAG_REQUEST_TRANSPARENT_REGIONS的属性
- 第二件事就是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的绘制的过程了。
网友评论