安卓复习之旅—View的工作流程1

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/lin_t_s/article/details/56668085

本篇文章主要是回顾一下安卓中view的工作原流程,view的工作流程主要包含measure、layout和draw三大流程,在进入主题之前,先要理解一下几个概念,以便更好的理解view的三大流程;

了解ViewRoot和DecorView
ViewRoot对应ViewRootImpl类,实现了ViewParent接口,它是连接WindowManager和DecorView的桥梁,WindowManager的实现类是WindowManagerImpl类;

public final class WindowManagerImpl implements WindowManager {
    private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
    private final Context mContext;
    private final Window mParentWindow;

    private IBinder mDefaultToken;

其中WindowManagerGlobal相当与一个代理类,WindowManagerImpl 中的实现的方法都是通过WindowManagerGlobal来具体实现的,我们来看看WindowManagerImpl 中的addView()方法:

@Override
    public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        applyDefaultToken(params);
        mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
    }

可以看出确实是通过WindowManagerGlobal的addView()方法来具体实现的,进入WindowManagerGlobal的addView()方法:

public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {
      ...
        final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
        ViewRootImpl root;
        View panelParentView = null;

        synchronized (mLock) {
            int index = findViewLocked(view, false);
            if (index >= 0) {
                if (mDyingViews.contains(view)) {
                    // Don't wait for MSG_DIE to make it's way through root's queue.
                    mRoots.get(index).doDie();
                } else {
                    throw new IllegalStateException("View " + view
                            + " has already been added to the window manager.");
                }
                // The previous removeView() had not completed executing. Now it has.
            }

            // If this is a panel window, then find the window it is being
            // attached to for future reference.
            if (wparams.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW &&
                    wparams.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
                final int count = mViews.size();
                for (int i = 0; i < count; i++) {
                    if (mRoots.get(i).mWindow.asBinder() == wparams.token) {
                        panelParentView = mViews.get(i);
                    }
                }
            }
            //实例化ViewRootImpl
            root = new ViewRootImpl(view.getContext(), display);

            view.setLayoutParams(wparams);

            mViews.add(view);
            mRoots.add(root);
            mParams.add(wparams);

            // do this last because it fires off messages to start doing things
            try {
                //连接WindowManager和DecorView
                root.setView(view, wparams, panelParentView);
            } catch (RuntimeException e) {
                // BadTokenException or InvalidDisplayException, clean up.
                if (index >= 0) {
                    removeViewLocked(index, true);
                }
                throw e;
            }
        }
    }

由上面注释的代码可以看出ViewRootImpl是连接WindowManager和DecorView的桥梁。

DecorView是FrameLayout的子类,它可以被认为是Android视图树的根节点视图。DecorView作为顶级View,一般情况下它内部包含一个竖直方向的LinearLayout,在这个LinearLayout里面有上下两个部分(具体情况和Android版本及主体有关),上面的是标题栏,下面的是内容栏。在Activity中通过setContentView所设置的布局文件其实就是被加到内容栏之中的,而内容栏的id是content,在代码中可以通过ViewGroup content = (ViewGroup)findViewById(R.android.id.content)来得到content对应的layout。
DecorView的结构图:
DecorView

View的绘制流程
View的绘制流程是从ViewRootImpl的performTraversals()方法开始的,它经过measure、layout和draw三个流程才能最终将view绘制出来,其中measure测量view的宽高,layout确定view在父容器中的位置,draw负责将view绘制在屏幕上。
view的绘制流程图:
这里写图片描述

performTraversals()方法源码比较长,就不贴出来了,有兴趣可以去查看一下,从上面的流程图可以看出performTraversals会依次调用 performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); performLayout(lp, mWidth, mHeight);performDraw();三个方法,这三个方法顶级view的三个流程,在 performMeasure()中会调用mView.measure()方法,mView.measure()又会调用onMeasure()方法对所有子元素进行测量,这个时候measure流程就从父容器传递到了子元素中,这样就完成了依次measure过程,performLayout()和performDraw()与performMeasure()过程是类似的,唯一的不同是performDraw()过程在draw方法中是通过dispatchDraw(canvas)方法来完成绘制的;

理解MeasureSpec
MeasureSpec是一个32位的int值,高2位代表SpecMode,低30位代表SpecSize,SpecMode是指测量模式,SpecSize是指某种测量模式下的规格大小;
MeasureSpec内部的一些常量:

        private static final int MODE_SHIFT = 30;
        private static final int MODE_MASK  = 0x3 << MODE_SHIFT;      
        public static final int UNSPECIFIED = 0 << MODE_SHIFT;       
        public static final int EXACTLY     = 1 << MODE_SHIFT;        
        public static final int AT_MOST     = 2 << MODE_SHIFT;

 public static int makeMeasureSpec(int size,int mode) {
            if (sUseBrokenMakeMeasureSpec) {
                return size + mode;
            } else {
                return (size & ~MODE_MASK) | (mode & MODE_MASK);
            }
        }
public static int getMode(int measureSpec) {
            //noinspection ResourceType
            return (measureSpec & MODE_MASK);
        }

 public static int getSize(int measureSpec) {
            return (measureSpec & ~MODE_MASK);
        }

measureSpec 将SpecMode和SpecSize打包成一个int值,一组SpecMode和SpecSize也可以打包成一个measureSpec 通过getMode和getSize可以得到对应的值;
SpecMode有三种类型:

  1. UNSPECIFIED
    父容器没有对view有任何限制,要多大给多大;
  2. EXACTLY
    父容器已经检测出view所需要的精确大小,view的大小就是SpecSize所指定的值,它对应于LayoutParams中的match_parent和具体的数值两种模式;
  3. AT_MOST
    父容器指定一个可用的大小即SpecSize,view的大小不能超过这个大小,对应于layoutParams中的wrap——content;

measureSpec 与LayoutParams有什么关系呢?当我们给view设置了LayoutParams后,系统会将LayoutParams在父容器的约束下转换成对应的measureSpec ;
对于DecorView来说,在ViewRootImpl中的measureHierarchy方法中进行了measureSpec 的创建过程,desiredWindowWidth为屏幕宽度,desiredWindowHeight为屏幕高度;

 childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);
            childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
            performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);

进入getRootMeasureSpec方法:

 private static int getRootMeasureSpec(int windowSize, int rootDimension) {
        int measureSpec;
        switch (rootDimension) {

        case ViewGroup.LayoutParams.MATCH_PARENT:
            // Window can't resize. Force root view to be windowSize.
            measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
            break;
        case ViewGroup.LayoutParams.WRAP_CONTENT:
            // Window can resize. Set max size for root view.
            measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
            break;
        default:
            // Window wants to be an exact size. Force root view to be that size.
            measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
            break;
        }
        return measureSpec;
    }

这个方法是根据DecorView自身的LayoutParams来计算出对应的measureSpec ;
对于普通view的measure过程是有viewgroup传递下来的,先看看viewgroup的measureChildWithMargins方法:

 protected void measureChildWithMargins(View child,
            int parentWidthMeasureSpec, int widthUsed,
            int parentHeightMeasureSpec, int heightUsed) {
        final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();

        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
                mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
                        + widthUsed, lp.width);
        final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
                mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
                        + heightUsed, lp.height);

        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }

measureChildWithMargins方法会通过getChildMeasureSpec方法计算出子元素的MeasureSpec然后子元素进行measure,从代码可以看出子元素的MeasureSpec与父容器的MeasureSpec与自身的LayoutParams有关,此外,还与view的margin和padding有关,下面进入getChildMeasureSpec方法:

public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
        int specMode = MeasureSpec.getMode(spec);
        int specSize = MeasureSpec.getSize(spec);

        int size = Math.max(0, specSize - padding);

        int resultSize = 0;
        int resultMode = 0;

        switch (specMode) {
        // Parent has imposed an exact size on us
        case MeasureSpec.EXACTLY:
            if (childDimension >= 0) {
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                // Child wants to be our size. So be it.
                resultSize = size;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                // Child wants to determine its own size. It can't be
                // bigger than us.
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            }
            break;

        // Parent has imposed a maximum size on us
        case MeasureSpec.AT_MOST:
            if (childDimension >= 0) {
                // Child wants a specific size... so be it
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                // Child wants to be our size, but our size is not fixed.
                // Constrain child to not be bigger than us.
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                // Child wants to determine its own size. It can't be
                // bigger than us.
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            }
            break;

        // Parent asked to see how big we want to be
        case MeasureSpec.UNSPECIFIED:
            if (childDimension >= 0) {
                // Child wants a specific size... let him have it
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                // Child wants to be our size... find out how big it should
                // be
                resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
                resultMode = MeasureSpec.UNSPECIFIED;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                // Child wants to determine its own size.... find out how
                // big it should be
                resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
                resultMode = MeasureSpec.UNSPECIFIED;
            }
            break;
        }
        //noinspection ResourceType
        return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
    }

这个方法主要作用是根据父容器的MeasureSpec与view自身的LayoutParams来确定子元素的MeasureSpec,参数中的padding指父容器已占用的大小;

展开阅读全文

没有更多推荐了,返回首页