欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

AndroidQ 图形系统(9)SurfaceView实现原理之设置透明区域

程序员文章站 2022-08-03 11:48:40
SurfaceView概述SurfaceView是一种特殊的View,它可以并且应该在子线程进行UI绘制,它具有独立于应用程序之外的surface,主要用来处理复杂,耗时的UI绘制,如视频播放,camera预览,游戏等,SurfaceView的默认Z-order低于应用程序主窗口,为APPLICATION_MEDIA_SUBLAYER = -2,可以通过setZOrderMediaOverlay或者setZOrderOnTop方法进行修改,SurfaceView采用设置透明区域的方式显示出自己,调用的方法...

SurfaceView概述

SurfaceView是一种特殊的View,它可以并且应该在子线程进行UI绘制,它具有独立于应用程序之外的surface,主要用来处理复杂,耗时的UI绘制,如视频播放,camera预览,游戏等,SurfaceView的默认Z-order低于应用程序主窗口,为APPLICATION_MEDIA_SUBLAYER = -2,意味着SurfaceView其实默认就是用来播放视频的,可以通过setZOrderMediaOverlay或者setZOrderOnTop方法进行修改,SurfaceView采用设置透明区域的方式显示出自己,调用的方法是requestTransparentRegion方法,后面我们会依次分析SurfaceView的创建,设置透明区域,及其内部Surface的创建,以及绘制的原理。

我们先来看看一段代码:
MainActivity.java

package com.example.surfaceviewdemo;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
public class MainActivity extends AppCompatActivity {
    private SurfaceView mSurfaceView;
    private SurfaceHolder mSurfaceHolder;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mSurfaceView = findViewById(R.id.surfaceView);
        mSurfaceHolder = mSurfaceView.getHolder();
        mSurfaceHolder.addCallback(new MyHolderCallback());
    }
    class MyHolderCallback implements SurfaceHolder.Callback{

        @Override
        public void surfaceCreated(SurfaceHolder holder) {
            Log.d("dongjiao","surfaceCreated....");
        }
        @Override
        public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
            Log.d("dongjiao","surfaceChanged....");
        }
        @Override
        public void surfaceDestroyed(SurfaceHolder holder) {
            Log.d("dongjiao","surfaceDestroyed....");
        }
    }
}

布局文件:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <SurfaceView
        android:id="@+id/surfaceView"
        android:layout_width="200dp"
        android:layout_height="200dp"/>

</LinearLayout>

运行之后:
AndroidQ 图形系统(9)SurfaceView实现原理之设置透明区域
AndroidQ 图形系统(9)SurfaceView实现原理之设置透明区域
这是一个最简单的SurfaceView的用法,我们什么也没画,仅仅将它创建出来,SurfaceHolder用于控制SurfaceView生命周期并且回调,我们先暂时不管SurfaceView如何进行绘制的,现在来研究它是如何创建出来的,并且创建过程都做了什么。

SurfaceView再怎么特殊它也是一个View,可以像对待其他任何 View 一样对其进行操作,我们知道一个Activity启动之后会经历View的测量,布局,绘制流程,入口在ViewRootImplperformTraversals方法中,SurfaceView和普通View一样也会经历这个流程,但不会走绘制,为何?来看SurfaceView构造方法中的一句关键代码:

public SurfaceView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        mRenderNode.addPositionUpdateListener(mPositionListener);
        setWillNotDraw(true);
    }

所以你会发现就算重写了SurfaceView的onDraw方法也不会调用,我们接着来看performTraversals这个方法。

performTraversals

这个方法代码相当的多,我们只关注两个View生命周期回调方法

private void performTraversals() {
	final View host = mView;
	......
	if (mFirst) {
		...
		host.dispatchAttachedToWindow(mAttachInfo, 0);
		...
	}
	...
	if (viewVisibilityChanged) {
		...
		host.dispatchWindowVisibilityChanged(viewVisibility);
		...
	}
	.....
	performMeasure();
	...
	performLayout();
	...
	performDraw();
	...
}

host代表当前Activity的顶层视图DecorView,当一个View附加到其父视图时提供给该View的一组窗口信息就是mAttachInfo,会传递给每一个View。

DecorView和其父类FrameLayout都没有重写dispatchAttachedToWindow方法,我们来看ViewGroup的这个方法:

dispatchAttachedToWindow

    @Override
    @UnsupportedAppUsage
    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和ViewGroup中非常常见,一层一层的遍历View树。

SurfaceView中并没有重写dispatchAttachedToWindow方法,接着来看View的此方法。

@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
    void dispatchAttachedToWindow(AttachInfo info, int visibility) {
        mAttachInfo = info;
        if (mOverlay != null) {
            mOverlay.getOverlayView().dispatchAttachedToWindow(info, visibility);
        }
        mWindowAttachCount++;
        .....
        onAttachedToWindow();
        ......
        // Send onVisibilityChanged directly instead of dispatchVisibilityChanged.
        // As all views in the subtree will already receive dispatchAttachedToWindow
        // traversing the subtree again here is not desired.
        onVisibilityChanged(this, visibility);
		......
    }

这里面又调用了两个我们常用的回调onAttachedToWindowonVisibilityChangedSurfaceView中没有重写onVisibilityChanged方法,但是重写了onAttachedToWindow方法,我们接着就来看看这个方法:

SurfaceView.onAttachedToWindow

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

此方法中会添加一些监听器,暂时不去看,
updateRequestedVisibility很简单,就是更新mRequestedVisible的值,最重要的还是requestTransparentRegion方法,因为SurfaceView默认层级在主窗口之下,为了能够可见需要设置透明区域,就是依靠requestTransparentRegion方法实现的。

mParent.requestTransparentRegion

mParent代表当前SurfaceView父容器,其实不管父容器是LinearLayout,FrameLayout还是其他,他们都没有重写requestTransparentRegion方法,所以最终都是调用到ViewGroup的requestTransparentRegion方法:

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

这里的child就是SurfaceView,首先会给SurfaceView设置Flag为PFLAG_REQUEST_TRANSPARENT_REGIONS,然后调用SurfaceView的父容器的mParent的requestTransparentRegion方法,mParent会一直向上寻找父容器,直到找到顶层视图DecorView的mParent—>ViewRootImpl,所以最终其实是调用到ViewRootImpl的requestTransparentRegion方法:

ViewRootImpl.requestTransparentRegion

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

首先会检查线程,这个操作必须在主线程,接着如果是正常调用的话,child就是DecorView了,给DecorView的mPrivateFlags设置为PFLAG_REQUEST_TRANSPARENT_REGIONS,修改mWindowAttributesChanged和mWindowAttributesChangesFlag并调用requestLayout()方法触发UI重绘,但重绘并不是立即执行的,而是将绘制的Runnable放入队列,需要等待下次Vsync到来才能执行,最终执行的就是performTraversals方法,注意,这次的performTraversals是第二次,第一次的我们还没分析完,第一次回调SurfaceViewonAttachedToWindow方法,此方法中设置的透明区域,第二次为什么不会再走到onAttachedToWindow方法去,这是因为performTraversals中有个开关mFirst,我们接着来看第二次performTraversals中设置透明区域的代码逻辑:

performTraversals

private void performTraversals() {
	final View host = mView;
	......
	if (mFirst) {
		...
		host.dispatchAttachedToWindow(mAttachInfo, 0);
		...
	}
	...
	if (viewVisibilityChanged) {
		...
		host.dispatchWindowVisibilityChanged(viewVisibility);
		...
	}
	.....
	performMeasure();
	...
	if (didLayout) {
            performLayout(lp, mWidth, mHeight);

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

            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) {
                    }
                }
            }
	
	...
	performDraw();
	...
}

设置透明区域是在Layout之后执行的,必须要等到整个视图树的所有View位置确定之后才能知道要设置的透明区域在什么位置.

接着DecorView的flag为PFLAG_REQUEST_TRANSPARENT_REGIONS则开始透明区域的设置,其实下面这两个方法合起来就是将mTransparentRegion设置为当前DecorView的占据的区域:

      host.getLocationInWindow(mTmpLocation);
                
      mTransparentRegion.set(mTmpLocation[0], mTmpLocation[1],
                        mTmpLocation[0] + host.mRight - host.mLeft,
                        mTmpLocation[1] + host.mBottom - host.mTop);

mTmpLocation是一个大小为2的int数组,这个数组会传递到View中去,接收调用getLocationInWindow方法的View的mLeftmTop返回,这里即是DecorView的mLeftmTop,这两个值其实就是DecorView左上角点的坐标,所以getLocationInWindow方法其实就是计算View的左上角坐标并以int数组返回,然后mTransparentRegion就以DecorView的左上右下四个点创建了一个矩形区域作为初始化透明区域,接着调用了DecorView的gatherTransparentRegion方法:

gatherTransparentRegion

@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的gatherTransparentRegion方法根本没有具体实现,最终都会调到ViewGroup或者View中,前面说过这种模式在ViewGroup或者View非常多,其实就是遍历视图树,ViewGroup中果然又是遍历子View,对于flag为PFLAG_REQUEST_TRANSPARENT_REGIONS的ViewGroup则调用所有子View的gatherTransparentRegion方法:

@Override
    public boolean gatherTransparentRegion(Region region) {
        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);
       
        
        .....
            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;
                    }
                }
            }
           ...
        }
        return meOpaque || noneOfTheChildrenAreTransparent;
    }

在遍历子View之前首先调用了super.gatherTransparentRegion
所以还得来看下View的gatherTransparentRegion方法:

@UnsupportedAppUsage
    public boolean gatherTransparentRegion(Region region) {
        final AttachInfo attachInfo = mAttachInfo;
        if (region != null && attachInfo != null) {
            final int pflags = mPrivateFlags;
            if ((pflags & PFLAG_SKIP_DRAW) == 0) {
                
                final int[] location = attachInfo.mTransparentLocation;
                getLocationInWindow(location);
               
                int shadowOffset = getZ() > 0 ? (int) getZ() : 0;
                region.op(location[0] - shadowOffset, location[1] - shadowOffset,
                        location[0] + mRight - mLeft + shadowOffset,
                        location[1] + mBottom - mTop + (shadowOffset * 3), Region.Op.DIFFERENCE);
            } else {
                if (mBackground != null && mBackground.getOpacity() != PixelFormat.TRANSPARENT) {
                  
                    applyDrawableToTransparentRegion(mBackground, region);
                }
                if (mForegroundInfo != null && mForegroundInfo.mDrawable != null
                        && mForegroundInfo.mDrawable.getOpacity() != PixelFormat.TRANSPARENT) {
                    // Similarly, we remove the foreground drawable's non-transparent parts.
                    applyDrawableToTransparentRegion(mForegroundInfo.mDrawable, region);
                }
                if (mDefaultFocusHighlight != null
                        && mDefaultFocusHighlight.getOpacity() != PixelFormat.TRANSPARENT) {
                    // Similarly, we remove the default focus highlight's non-transparent parts.
                    applyDrawableToTransparentRegion(mDefaultFocusHighlight, region);
                }
            }
        }
        return true;
    }

这里面有一个很重要的值,就是mPrivateFlags,如果flag被设置为了PFLAG_SKIP_DRAW代表这个View不需要绘制,什么情况会被设置为PFLAG_SKIP_DRAW,通常是调用setWillNotDraw(true)设置的,SurfaceView的构造方法中就是调用了此方法,如果是一个需要绘制的View,则会将这个View所占据的矩形区域从mTransparentRegion透明区域中裁掉,裁剪的方式是Region.Op.DIFFERENCE,因为一开始从ViewRootImpl传递过来的mTransparentRegion是等于DecorView的大小,这里DecorView是需要绘制的,所以相当于整个DecorView没有收集到透明区域,接着对于View不需要绘制的情况例如SurfaceView,首先如果此View的mBackground不为空并且mBackground不为透明,则会调用applyDrawableToTransparentRegion方法同样会将此View所占据的矩形区域从region中裁剪掉,代表此View不会在主窗口上设置透明区域,applyDrawableToTransparentRegion中裁剪格式很有几种:Region.Op.UNIONRegion.Op.INTERSECTRegion.Op.DIFFERENCE,会根据具体条件来选择,关于裁剪格式借用博客https://blog.csdn.net/eyishion/article/details/53728913的一张图:
AndroidQ 图形系统(9)SurfaceView实现原理之设置透明区域
SurfaceView之前整个View树还没有收集到透明区域,这是因为其他View默认都是需要绘制的,所以他们都从mTransparentRegion中裁剪掉了。

接着我们需要看看SurfaceView中关于gatherTransparentRegion的具体实现:

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

首先如果isAboveParent为true,代表着SurfaceViewmSubLayer大于0,默认SurfaceViewmSubLayer是等于APPLICATION_MEDIA_SUBLAYER为-2的,所以isAboveParent默认值为false,可通过setZOrderOnTop或者setZOrderMediaOverlay修改,mDrawFinished代表已经完成绘制,因为设置透明区域是通过requestLayout请求下一个Vsync执行的,所以上一个Vsync已经执行完绘制流程了,mDrawFinished为true,所以默认情况下不会进这个判断,接着判断SurfaceView是否需要绘制,即flag是否为PFLAG_SKIP_DRAW,默认情况下不会走进这里,除非手动调用setWillNotDraw(false),综上,默认情况下不会调用父类View的gatherTransparentRegion方法,接着获取SurfaceView的宽高,调用getLocationInWindow获取SurfaceView左上角坐标放入mLocation中,并裁剪出当前SurfaceView的矩形区域放入mTransparentRegion中,最后还会判断当前正在处理的SurfaceView的格式是否设置有透明值,如果有则将opaque设置为false并返回。

最后回到performTraversalsmTransparentRegion有了之后会设置给mPreviousTransparentRegion,避免在透明区域没有改动时重复调用,接着通过
mWindowSession.setTransparentRegion(mWindow, mTransparentRegion)将mTransparentRegion传给WMS,最终传递到SurfaceFlinger中去修改当前Activity的surface的透明区域。

到此SurfaceViewonAttachedToWindow方法中设置透明区域已经全部分析完毕了。接下来需要看SurfaceView的另一个回调方法onWindowVisibilityChanged所做的事情了。

本文地址:https://blog.csdn.net/qq_34211365/article/details/107045611