2. SurfaceView的挖洞过程

SurfaceView的窗口类型一般都是TYPE\_APPLICATION\_MEDIA或者TYPE\_APPLICATION\_MEDIA\_OVERLAY,也就是说,它的Z轴位置是小于其宿主窗口的Z位置的。为了保证SurfaceView的UI是可见的,SurfaceView就需要在其宿主窗口的上面挖一个洞出来,实际上就是在其宿主窗口的绘图表面上设置一块透明区域,以便可以将自己显示出来。

从SurfaceView的绘图表面的创建过程可以知道,SurfaceView在被附加到宿主窗口之上的时候,会请求在宿主窗口上设置透明区域,而每当其宿主窗口刷新自己的UI的时候,就会将所有嵌入在它里面的SurfaceView所设置的透明区域收集起来,然后再通知WindowManagerService服务为其设置一个总的透明区域。

从SurfaceView的绘图表面的创建过程可以知道,SurfaceView在被附加到宿主窗口之上的时候,SurfaceView类的成员函数onAttachedToWindow就会被调用。SurfaceView类的成员函数onAttachedToWindow在被调用的期间,就会请求在宿主窗口上设置透明区域。接下来,我们就从SurfaceView类的成员函数onAttachedToWindow开始,分析SurfaceView的挖洞过程,如图3所示:

图3 SurfaceView的挖洞过程

这个过程可以分为6个步骤,接下来我们就详细分析每一个步骤。

Step 1. SurfaceView.onAttachedToWindow

public class SurfaceView extends View {
    ......

    @Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();
        mParent.requestTransparentRegion(this);
        ......
    }

    ......
}

这个函数定义在文件frameworks/base/core/java/android/view/SurfaceView.java中。

SurfaceView类的成员变量mParent是从父类View继承下来的,用来描述当前正在处理的SurfaceView的父视图。我们假设当前正在处理的SurfaceView的父视图就为其宿主窗口的顶层视图,因此,接下来SurfaceView类的成员函数onAttachedToWindow就会调用DecorView类的成员函数requestTransparentRegion来请求在宿主窗口之上挖一个洞。

DecorView类的成员函数requestTransparentRegion是从父类ViewGroup继承下来的,因此,接下来我们就继续分析ViewGroup类的成员函数requestTransparentRegion的实现。

Step 2. ViewGroup.requestTransparentRegion

public abstract class ViewGroup extends View implements ViewParent, ViewManager {
    ......

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

    ......
}

这个函数定义在文件frameworks/base/core/java/android/view/ViewGroup.java中。

参数child描述的便是要在宿主窗口设置透明区域的SurfaceView,ViewGroup类的成员函数requestTransparentRegion首先将它的成员变量mPrivateFlags的值的View.REQUEST\_TRANSPARENT\_REGIONS位设置为1,表示它要在宿主窗口上设置透明区域,接着再调用从父类View继承下来的成员变量mParent所指向的一个视图容器的成员函数requestTransparentRegion来继续向上请求设置透明区域,这个过程会一直持续到当前正在处理的视图容器为窗口的顶层视图为止。

前面我们已经假设了参数child所描述的SurfaceView是直接嵌入在宿主窗口的顶层视图中的,而窗口的顶层视图的父视图是使用一个ViewRoot对象来描述的,也就是说,当前正在处理的视图容器的成员变量mParent指向的是一个ViewRoot对象,因此,接下来我们就继续分析ViewRoot类的成员函数requestTransparentRegion的实现,以便可以继续了解SurfaceView的挖洞过程。

Step 3. ViewRoot.requestTransparentRegion

public final class ViewRoot extends Handler implements ViewParent,
        View.AttachInfo.Callbacks {
    ......

    public void requestTransparentRegion(View child) {
        // the test below should not fail unless someone is messing with us
        checkThread();
        if (mView == child) {
            mView.mPrivateFlags |= View.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;
            requestLayout();
        }
    }

    ......
}

这个函数定义在文件frameworks/base/core/java/android/view/ViewRoot.java中。

ViewRoot类的成员函数requestTransparentRegion首先调用另外一个成员函数checkThread来检查当前执行的线程是否是应用程序的主线程,如果不是的话,那么就会抛出一个类型为CalledFromWrongThreadException的异常。

通过了上面的检查之后,ViewRoot类的成员函数requestTransparentRegion再检查参数child所描述的视图是否就是当前正在处理的ViewRoot对象所关联的窗口的顶层视图,即检查它与ViewRoot类的成员变量mView是否是指向同一个View对象。由于一个ViewRoot对象有且仅有一个子视图,因此,如果上述检查不通过的话,那么就说明调用者正在非法调用ViewRoot类的成员函数requestTransparentRegion来设置透明区域。

通过了上述两个检查之后,ViewRoot类的成员函数requestTransparentRegion就将成员变量mView所描述的一个窗口的顶层视图的成员变量mPrivateFlags的值的View.REQUEST\_TRANSPARENT\_REGIONS位设置为1,表示该窗口被设置了一块透明区域。

当一个窗口被请求设置了一块透明区域之后,它的窗口属性就发生变化了,因此,这时候除了要将与它所关联的一个ViewRoot对象的成员变量mWindowAttributesChanged的值设置为true之外,还要调用该ViewRoot对象的成员函数requestLayout来请求刷新一下窗口的UI,即请求对窗口的UI进行重新布局和绘制。

从前面

Android应用程序窗口(Activity)的绘图表面(Surface)的创建过程分析

一文可以知道,ViewRoot类的成员函数requestLayout最终会调用到另外一个成员函数performTraversals来实际执行刷新窗口UI的操作。ViewRoot类的成员函数performTraversals在刷新窗口UI的过程中,就会将嵌入在它里面的SurfaceView所要设置的透明区域收集起来,以便可以请求WindowManagerService将这块透明区域设置到它的绘图表面上去。

接下来,我们就继续分析ViewRoot类的成员函数performTraversals的实现,以便可以继续了解SurfaceView的挖洞过程。

Step 4. ViewRoot.performTraversals

public final class ViewRoot extends Handler implements ViewParent,
        View.AttachInfo.Callbacks {
    ......

    private void performTraversals() {
        ......

        // cache mView since it is used so much below...
        final View host = mView;
        ......

        final boolean didLayout = mLayoutRequested;
        ......

        if (didLayout) {
            ......

            host.layout(0, 0, host.mMeasuredWidth, host.mMeasuredHeight);

            ......

            if ((host.mPrivateFlags & View.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 (!mTransparentRegion.equals(mPreviousTransparentRegion)) {
                    mPreviousTransparentRegion.set(mTransparentRegion);
                    // reconfigure window manager
                    try {
                        sWindowSession.setTransparentRegion(mWindow, mTransparentRegion);
                    } catch (RemoteException e) {
                    }
                }
            }

            ......
        }

        ......

        boolean cancelDraw = attachInfo.mTreeObserver.dispatchOnPreDraw();

        if (!cancelDraw && !newSurface) {
            ......

            draw(fullRedrawNeeded);

            ......
        } 

        ......
    }

    ......
}

这个函数定义在文件frameworks/base/core/java/android/view/ViewRoot.java中。

ViewRoot类的成员函数performTraversals的具体实现可以参考前面

Android应用程序窗口(Activity)实现框架简要介绍和学习计划

这个系列的文章以及

Android窗口管理服务WindowManagerService计算Activity窗口大小的过程分析

一文,这里我们只关注窗口收集透明区域的逻辑。

ViewRoot类的成员函数performTraversals是在窗口的UI布局完成之后,并且在窗口的UI绘制之前,收集嵌入在它里面的SurfaceView所设置的透明区域的,这是因为窗口的UI布局完成之后,各个子视图的大小和位置才能确定下来,这样SurfaceView才知道自己要设置的透明区域的位置和大小。

变量host与ViewRoot类的成员变量mView指向的是同一个DecorView对象,这个DecorView对象描述的便是当前正在处理的窗口的顶层视图。从前面的Step 3可以知道,如果当前正在处理的窗口的顶层视图内嵌有SurfaceView,那么用来描述它的一个DecorView对象的成员变量mPrivateFlags的值的View.REQUEST\_TRANSPARENT\_REGIONS位就会等于1。在这种情况下,ViewRoot类的成员函数performTraversals就知道需要在当前正在处理的窗口的上面设置一块透明区域了。这块透明区域的收集过程如下所示:

  • 1.计算顶层视图的位置和大小,即计算顶层视图所占据的区域。
  • 2.将顶层视图所占据的区域作为窗口的初始化透明区域,保存在ViewRoot类的成员变量mTransparentRegion中。
  • 3.从顶层视图开始,从上到下收集每一个子视图所要设置的区域,最终收集到的总透明区域也是保存在ViewRoot类的成员变量mTransparentRegion中。
  • 4.检查ViewRoot类的成员变量mTransparentRegion和mPreviousTransparentRegion所描述的区域是否相等。如果不相等的话,那么就说明窗口的透明区域发生了变化,这时候就需要调用ViewRoot类的的静态成员变量sWindowSession所描述的一个Binder代理对象的成员函数setTransparentRegion通知WindowManagerService为窗口设置由成员变量mTransparentRegion所指定的透明区域。

其中,第(3)步是通过调用变量host所描述的一个DecorView对象的成员函数gatherTransparentRegion来实现的。 DecorView类的成员函数gatherTransparentRegion是从父类ViewGroup继承下来的,因此,接下来我们就继续分析ViewGroup类的成员函数gatherTransparentRegion的实现,以便可以了解SurfaceView的挖洞过程。

Step 5. ViewGroup.gatherTransparentRegion

public abstract class ViewGroup extends View implements ViewParent, ViewManager {
    ......

    @Override
    public boolean gatherTransparentRegion(Region region) {
        // If no transparent regions requested, we are always opaque.
        final boolean meOpaque = (mPrivateFlags & View.REQUEST_TRANSPARENT_REGIONS) == 0;
        if (meOpaque && region == null) {
            // The caller doesn't care about the region, so stop now.
            return true;
        }
        super.gatherTransparentRegion(region);
        final View[] children = mChildren;
        final int count = mChildrenCount;
        boolean noneOfTheChildrenAreTransparent = true;
        for (int i = 0; i < count; i++) {
            final View child = children[i];
            if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
                if (!child.gatherTransparentRegion(region)) {
                    noneOfTheChildrenAreTransparent = false;
                }
            }
        }
        return meOpaque || noneOfTheChildrenAreTransparent;
    }

    ......
}

这个函数定义在文件frameworks/base/core/java/android/view/ViewGroup.java中。

ViewGroup类的成员函数gatherTransparentRegion首先是检查当前正在处理的视图容器是否被请求设置透明区域,即检查成员变量mPrivateFlags的值的 View.REQUEST_TRANSPARENT_REGIONS位是否等于1。如果不等于1,那么就说明不用往下继续收集窗口的透明区域了,因为在这种情况下,当前正在处理的视图容器及其子视图都不可能设置有透明区域。另一方面,如果参数region的值等于null,那么就说明调用者不关心当前正在处理的视图容器的透明区域,而是关心它是透明的,还是不透明的。在上述两种情况下,ViewGroup类的成员函数gatherTransparentRegion都不用进一步处理了。

假设当前正在处理的视图容器被请求设置有透明区域,并且参数region的值不等于null,那么接下来ViewGroup类的成员函数gatherTransparentRegion就执行以下两个操作:

(1). 调用父类View的成员函数gatherTransparentRegion来检查当前正在处理的视图容器是否需要绘制。如果需要绘制的话,那么就会将它所占据的区域从参数region所占据的区域移除,这是因为参数region所描述的区域开始的时候是等于窗口的顶层视图的大小的,也就是等于窗口的整个大小的。

(2). 调用当前正在处理的视图容器的每一个子视图的成员函数gatherTransparentRegion来继续往下收集透明区域。

在接下来的Step 6中,我们再详细分析当前正在处理的视图容器的每一个子视图的透明区域的收集过程,现在我们主要分析View类的成员函数gatherTransparentRegion的实现,如下所示:

public class View implements Drawable.Callback, KeyEvent.Callback, AccessibilityEventSource {
    ......

    public boolean gatherTransparentRegion(Region region) {
        final AttachInfo attachInfo = mAttachInfo;
        if (region != null && attachInfo != null) {
            final int pflags = mPrivateFlags;
            if ((pflags & SKIP_DRAW) == 0) {
                // The SKIP_DRAW flag IS NOT set, so this view draws. We need to
                // remove it from the transparent region.
                final int[] location = attachInfo.mTransparentLocation;
                getLocationInWindow(location);
                region.op(location[0], location[1], location[0] + mRight - mLeft,
                        location[1] + mBottom - mTop, Region.Op.DIFFERENCE);
            } else if ((pflags & ONLY_DRAWS_BACKGROUND) != 0 && mBGDrawable != null) {
                // The ONLY_DRAWS_BACKGROUND flag IS set and the background drawable
                // exists, so we remove the background drawable's non-transparent
                // parts from this transparent region.
                applyDrawableToTransparentRegion(mBGDrawable, region);
            }
        }
        return true;
    }

    ......
}

这个函数定义在文件frameworks/base/core/java/android/view/View.java中。

View类的成员函数gatherTransparentRegion首先是检查当前正在处理的视图的前景是否需要绘制,即检查成员变量mPrivateFlags的值的SKIP_DRAW位是否等于0。如果等于0的话,那么就说明当前正在处理的视图的前景是需要绘制的。在这种情况下,View类的成员函数gatherTransparentRegion就会将当前正在处理的视图所占据的区域从参数region所描述的区域中移除,以便当前正在处理的视图的前景可以显示出来。

另一方面,如果当前正在处理的视图的前景不需要绘制,但是该视图的背景需要绘制,并且该视图是设置有的,即成员变量mPrivateFlags的值的SKIP_DRAW位不等于0,并且成员变量mBGDrawable的值不等于null,这时候View类的成员函数gatherTransparentRegion就会调用另外一个成员函数applyDrawableToTransparentRegion来将该背景中的不透明区域从参数region所描述的区域中移除,以便当前正在处理的视图的背景可以显示出来。

回到ViewGroup类的成员函数gatherTransparentRegion中,当前正在处理的视图容器即为当前正在处理的窗口的顶层视图,前面我们已经假设它里面嵌入有一个SurfaceView子视图,因此,接下来就会收集该SurfaceView子视图所设置的透明区域,这是通过调用SurfaceView类的成员函数gatherTransparentRegion来实现的。

接下来,我们就继续分析SurfaceView类的成员函数gatherTransparentRegion的实现,以便可以继续了解SurfaceView的挖洞过程。

Step 6. SurfaceView.gatherTransparentRegion

public class SurfaceView extends View {
    ......

    @Override
    public boolean gatherTransparentRegion(Region region) {
        if (mWindowType == WindowManager.LayoutParams.TYPE_APPLICATION_PANEL) {
            return super.gatherTransparentRegion(region);
        }

        boolean opaque = true;
        if ((mPrivateFlags & 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;
    }

    ......
}

这个函数定义在文件frameworks/base/core/java/android/view/SurfaceView.java中。

SurfaceView类的成员函数gatherTransparentRegion首先是检查当前正在处理的SurfaceView是否是用作窗口面板的,即它的成员变量mWindowType的值是否等于WindowManager.LayoutParams.TYPE_APPLICATION_PANEL。如果等于的话,那么就会调用父类View的成员函数gatherTransparentRegion来检查该面板是否需要绘制。如果需要绘制,那么就会将它所占据的区域从参数region所描述的区域移除。

假设当前正在处理的SurfaceView不是用作窗口面板的,那么SurfaceView类的成员函数gatherTransparentRegion接下来就会直接检查当前正在处理的SurfaceView是否是需要在宿主窗口的绘图表面上进行绘制,即检查成员变量mPrivateFlags的值的SKIP_DRAW位是否等于1。如果需要的话,那么也会调用父类View的成员函数gatherTransparentRegion来将它所占据的区域从参数region所描述的区域移除。

假设当前正在处理的SurfaceView不是用作窗口面板,并且也是不需要在宿主窗口的绘图表面上进行绘制的,而参数region的值又不等于null,那么SurfaceView类的成员函数gatherTransparentRegion就会先计算好当前正在处理的SurfaceView所占据的区域,然后再将该区域添加到参数region所描述的区域中去,这样就可以得到窗口的一个新的透明区域。

最后,SurfaceView类的成员函数gatherTransparentRegion判断当前正在处理的SurfaceView的绘图表面的像素格式是否设置有透明值。如果有的话,那么就会将变量opaque的值设置为false,否则的话,变量opaque的值就保持为true。变量opaque的值最终会返回给调用者,这样调用者就可以知道当前正在处理的SurfaceView的绘图表面是否是半透明的了。

至此,我们就分析完成SurfaceView的挖洞过程了,接下来我们继续分析SurfaceView的绘制过程。

results matching ""

    No results matching ""