Android View工作原理

前言

在Android知识体系中,View扮演了很重要的角色;它是Android在视觉上的呈现,Android本身提供了一套GUI库,但是我们的需求不止于系统自带的GUI,因此我们还需要自定义View。而自定义View过程中我们势必要对View的底层工作原理有所了解。这篇文章就记录一下View的测量、布局、绘制三大流程。

1.一些必要的基本概念

1.1 ViewRoot和DecorView

ViewRoot对应于ViewRootImpl类,连接WindowManagerDecorView的纽带;View的三大流程基于RootView来完成的;View的绘制流程是从ViewRoot的performTraversals方法开始的。

DecorView顶级View,内部一般为一个竖直方向的LinearLayout,分为2部分:titlebar以及android.R.id.content的FrameLayout。
结构图如下:
在这里插入图片描述

1.2 MeasureSpec

字面意思为“测量规格”;

它代表一个32位int值,高2位代表SpecMode(测量模式),低30位代表SpecSize(某种测量模式下的规格大小)。

SpecMode一共有3类:

  1. EXACTLY:match_parent和具体数值,父容器已经检测出View的精确大小
  2. AT_MOST:父容器制定一个可用大小,View的大小不能大于它
  3. UNSPECIFIED: 父容器不对View有任何限制

一般来说,View的MeasureSpec由本身的LayoutParams与父容器的MeasureSpec共同决定。DecorView的MeasureSpec由LayoutParams决定。父容器通过measure传递MeasureSpec给子View.

引用刚哥的《Android 开发艺术探索》的图:这张图涵括了View Measure中最核心的过程。

在这里插入图片描述

它在View的底层原理中起到的作用下面的篇幅在做具体的介绍。源码如下:所在包import android.view.View.MeasureSpec;

 /**
     * A MeasureSpec encapsulates the layout requirements passed from parent to child.
     * Each MeasureSpec represents a requirement for either the width or the height.
     * A MeasureSpec is comprised of a size and a mode. There are three possible
     * modes:
     * <dl>
     * <dt>UNSPECIFIED</dt>
     * <dd>
     * The parent has not imposed any constraint on the child. It can be whatever size
     * it wants.
     * </dd>
     *
     * <dt>EXACTLY</dt>
     * <dd>
     * The parent has determined an exact size for the child. The child is going to be
     * given those bounds regardless of how big it wants to be.
     * </dd>
     *
     * <dt>AT_MOST</dt>
     * <dd>
     * The child can be as large as it wants up to the specified size.
     * </dd>
     * </dl>
     *
     * MeasureSpecs are implemented as ints to reduce object allocation. This class
     * is provided to pack and unpack the &lt;size, mode&gt; tuple into the int.
     */
    public static class MeasureSpec {
        private static final int MODE_SHIFT = 30;
        private static final int MODE_MASK  = 0x3 << MODE_SHIFT;
        /** @hide */
        @IntDef({UNSPECIFIED, EXACTLY, AT_MOST})
        @Retention(RetentionPolicy.SOURCE)
        public @interface MeasureSpecMode {}
        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(@IntRange(from = 0, to = (1 << MeasureSpec.MODE_SHIFT) - 1) int size,
                                          @MeasureSpecMode int mode) {
            if (sUseBrokenMakeMeasureSpec) {
                return size + mode;
            } else {
                return (size & ~MODE_MASK) | (mode & MODE_MASK);
            }
        }

        public static int makeSafeMeasureSpec(int size, int mode) {
            if (sUseZeroUnspecifiedMeasureSpec && mode == UNSPECIFIED) {
                return 0;
            }
            return makeMeasureSpec(size, mode);
        }

        public static int getMode(int measureSpec) {
            //noinspection ResourceType
            return (measureSpec & MODE_MASK);
        }

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

        static int adjust(int measureSpec, int delta) {
            final int mode = getMode(measureSpec);
            int size = getSize(measureSpec);
            if (mode == UNSPECIFIED) {
                // No need to adjust size for UNSPECIFIED mode.
                return makeMeasureSpec(size, UNSPECIFIED);
            }
            size += delta;
            if (size < 0) {
                Log.e(VIEW_LOG_TAG, "MeasureSpec.adjust: new size would be negative! (" + size +
                        ") spec: " + toString(measureSpec) + " delta: " + delta);
                size = 0;
            }
            return makeMeasureSpec(size, mode);
        }

        public static String toString(int measureSpec) {
            int mode = getMode(measureSpec);
            int size = getSize(measureSpec);

            StringBuilder sb = new StringBuilder("MeasureSpec: ");

            if (mode == UNSPECIFIED)
                sb.append("UNSPECIFIED ");
            else if (mode == EXACTLY)
                sb.append("EXACTLY ");
            else if (mode == AT_MOST)
                sb.append("AT_MOST ");
            else
                sb.append(mode).append(" ");

            sb.append(size);
            return sb.toString();
        }
    }

2.View的工作原理

上面介绍了View底层工作原理所必备的一些知识后,现在重点介绍View的工作原理:

View三大流程的入口(通过Log可以得知):是从onResume()之后开始的。跟踪ActivityThread的源码,在handleResumeActivity()中开始.

2.1 handleResumeActivity()
@Override
    public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
            String reason) {
        ......
        if (r.window == null && !a.mFinished && willBeVisible) {
            r.window = r.activity.getWindow();
            View decor = r.window.getDecorView();
            decor.setVisibility(View.INVISIBLE);
            ViewManager wm = a.getWindowManager();
            WindowManager.LayoutParams l = r.window.getAttributes();
            a.mDecor = decor;
            l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
            l.softInputMode |= forwardBit;
            if (r.mPreserveWindow) {
                a.mWindowAdded = true;
                r.mPreserveWindow = false;
                // Normally the ViewRoot sets up callbacks with the Activity
                // in addView->ViewRootImpl#setView. If we are instead reusing
                // the decor view we have to notify the view root that the
                // callbacks may have changed.
                ViewRootImpl impl = decor.getViewRootImpl();
                if (impl != null) {
                    impl.notifyChildRebuilt();
                }
            }
            if (a.mVisibleFromClient) {
                if (!a.mWindowAdded) {
                    a.mWindowAdded = true;
                    wm.addView(decor, l);
                } else {
                    // The activity will get a callback for this {@link LayoutParams} change
                    // earlier. However, at that time the decor will not be set (this is set
                    // in this method), so no action will be taken. This call ensures the
                    // callback occurs with the decor set.
                    a.onWindowAttributesChanged(l);
                }
            }

           ......
    }

,注意注释 // in addView->ViewRootImpl#setView. If we are instead reusing最终调用ViewRootImpl.setView();

2.2 ViewRootImpl.setView()
 /**
     * We have one child
     */
    public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
         ......
         requestLayout();
         ......
    }

进入requestLayout()方法。

2.3 ViewRootImpl.requestLayout()
@Override
    public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
            checkThread();
            mLayoutRequested = true;
            scheduleTraversals();
        }
    }
2.4 ViewRootImpl.performTraversals()
@Override
    public void performTraversals() {
      ......
      performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
      ......
      performLayout(lp, mWidth, mHeight);
      ......
      performDraw();
      ...... 
    }

核心方法:View的工作主要流程就在此方法中。

归纳上方的流程为下图:
在这里插入图片描述

  • measure 测量,决定了测量后的宽高
  • layout 布局,确定四个定点坐标以及View的实际宽高
  • draw 绘制,决定View的显示

依次调用performMeasure、performLayout、performDraw方法,完成顶级View的measure、layout、draw;而在measure、layout、draw又会调用oMeasure、onLayout、onDraw对其子元素重复递归上过程,直到view不为ViewGroup时。

3.View的measure过程

  1. View的measure
    具体流程:
    在这里插入图片描述

一系列的方法会设置View宽高的测量值。其中getSuggestedMinimumWidth()会根据是存在背景来返回最小宽度。

 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
    }

public static int getDefaultSize(int size, int measureSpec) {
        int result = size;
        int specMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);

        switch (specMode) {
        case MeasureSpec.UNSPECIFIED:
            result = size;
            break;
        case MeasureSpec.AT_MOST:
        case MeasureSpec.EXACTLY:
            result = specSize;
            break;
        }
        return result;
    }

 protected int getSuggestedMinimumWidth() {
        return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
    }

从getDefault方法中可得结论:
直接继承View的自定义控件需重写onMeasure方法并设置wrap_content时的自身大小,否则使用wrap_content就相当于使用match_content。

  1. ViewGroup的measure

而ViewGroup需要完成本身的measure过程外,还要遍历所有子元素的measure方法,递归执行。本身未重写onMeasure()方法,提供measureChildren()方法遍历测量View树。具体继承自它的类重写onMeasure()对子View进行测量。

首先要确定DecorView的MeasureSpec用来传递给子View进行生成MeasureSpec。DecorView通过getRootMeasureSpec确定本身测量模式以及大小。其中windowSize就是屏幕的宽高。

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

其次,通过measureChildren(int widthMeasureSpec, int heightMeasureSpec)遍历测量所有的View树。

// 遍历测量View树
protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
        final int size = mChildrenCount;
        final View[] children = mChildren;
        for (int i = 0; i < size; ++i) {
            final View child = children[i];
            if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
                measureChild(child, widthMeasureSpec, heightMeasureSpec);
            }
        }
    }

// 根据LayoutParams 以及 父View传递过来的MeasureSpec 创建自身的测量模式/规格
protected void measureChild(View child, int parentWidthMeasureSpec,
            int parentHeightMeasureSpec) {
        //  自身布局参数    
        final LayoutParams lp = child.getLayoutParams();
        // 获取当前所需测量child的宽度的测量模式
        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
                mPaddingLeft + mPaddingRight, lp.width);
        // 获取当前所需测量child的高度的测量模式       
        final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
                mPaddingTop + mPaddingBottom, lp.height);
		
		// 最终调用measure,判断是View还是ViewGroup
        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }

其中最重要的源码部分如下:此部分即可以归纳为上面引入的图片。

	// spec 父Spec padding值  childDimension为布局文件中设置的参数
  public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
  		// 父specMode
        int specMode = MeasureSpec.getMode(spec);
        // 父specSize 
        int specSize = MeasureSpec.getSize(spec);

		// 父实际可用的空间
        int size = Math.max(0, specSize - padding);
		// 最终size
        int resultSize = 0;
        // 最终模式
        int resultMode = 0;
		
		// 结合父测量模式以及自身LayoutParams来创建child的测量模式
        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);
    }
总结:
  • 如果View的宽高是固定值,无论父MeasureSpec为何值,View的测量模式都为EXACTLY,且specSize都为实际值
  • 如果View的宽高是wrap_content,无论父MeasureSpec为何值(EXACTLY/AT_MOST),View的测量模式都为AT_MOST,且specSize都不笨办法超过父实际的size
  • 如果View的宽高是match_parent,父测量模式为EXACTLY,子View的测量模式也为EXACTLY;父测量模式为AT_MOST,子View的测量模式也为AT_MOST
到此整个View的Measure过程就结束了,测量完成后可通过getMeasuredWidth/Height()获取到View的测量宽高。整个流程的流程图如下(省略了好多细节,大体上走了一遍流程):

在这里插入图片描述

4.View的layout过程

Layout的作用是确定子元素的位置,layout方法确定本身所在位置,onLayout确定所有子元素的位置。跟踪ViewRootImpl中的performLayout方法。

private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
            int desiredWindowHeight) {
    
		......
		// host 即为DecorView
        final View host = mView;
        if (host == null) {
            return;
        }
   	 	 ......
    	 host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
    	 ......
         
    }
4.1 View中的layout方法

首先初始化LTRB,其中l,t,r,b分别为View的四个顶点的位置。接着调用onLayout确定子元素的位置。onLayout与onMeasure相似,具体的实现在具体的View中重写即可。onLayout中遍历调用setChildFrame(),内部调用的是 child.layout(left, top, left + width, top + height);通过递归完成View树的布局过程。

  /**
     * <p>Derived classes should not override this method.
     * Derived classes with children should override
     * onLayout. In that method, they should
     * call layout on each of their children.</p>
     *
     * @param l Left position, relative to parent
     * @param t Top position, relative to parent
     * @param r Right position, relative to parent
     * @param b Bottom position, relative to parent
     */
    @SuppressWarnings({"unchecked"})
    public void layout(int l, int t, int r, int b) {
        if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) {
            onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);
            mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
        }

        int oldL = mLeft;
        int oldT = mTop;
        int oldB = mBottom;
        int oldR = mRight;

        boolean changed = isLayoutModeOptical(mParent) ?
                setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);  // 设置四个顶点的坐标。

        if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
        	// 调用onLayout确定所有子元素的位置
            onLayout(changed, l, t, r, b);

            if (shouldDrawRoundScrollbar()) {
                if(mRoundScrollbarRenderer == null) {
                    mRoundScrollbarRenderer = new RoundScrollbarRenderer(this);
                }
            } else {
                mRoundScrollbarRenderer = null;
            }

            mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;

            ListenerInfo li = mListenerInfo;
            if (li != null && li.mOnLayoutChangeListeners != null) {
                ArrayList<OnLayoutChangeListener> listenersCopy =
                        (ArrayList<OnLayoutChangeListener>)li.mOnLayoutChangeListeners.clone();
                int numListeners = listenersCopy.size();
                for (int i = 0; i < numListeners; ++i) {
                    listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);
                }
            }
        }
        ......
        ......
    }

把LinearLayout中onLayout的具体实现做分析:

@Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        if (mOrientation == VERTICAL) {
            layoutVertical(l, t, r, b);
        } else {
            layoutHorizontal(l, t, r, b);
        }
    }


// 具体的实现方法
void layoutVertical(int left, int top, int right, int bottom) {
        ......
        final int count = getVirtualChildCount();

        ......
        for (int i = 0; i < count; i++) {
            final View child = getVirtualChildAt(i);
            // 测量宽高
           final int childWidth = child.getMeasuredWidth();
           final int childHeight = child.getMeasuredHeight();
          	......
				// 为子元素指定对应的位置
				// 设置的就是测量的宽高
                setChildFrame(child, childLeft, childTop + getLocationOffset(child),
                        childWidth, childHeight);
            ......
            }
        }
    }

 private void setChildFrame(View child, int left, int top, int width, int height) {
        child.layout(left, top, left + width, top + height);
    }

5.View的draw过程

Draw过程相对比较简单,即将View绘制到屏幕上。从RootViewImpl中跟踪。观察源码:

	private void performDraw() {
        if (mAttachInfo.mDisplayState == Display.STATE_OFF && !mReportNextDraw) {
            return;
        } else if (mView == null) {
            return;
        }

  		......
        boolean canUseAsync = draw(fullRedrawNeeded);
        ......
    }

    private boolean draw(boolean fullRedrawNeeded) {

			......
           if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset,
                 scalingRequired, dirty, surfaceInsets)) {
               return false;
               }
			......
        return useAsyncReport;
    }



    private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
            boolean scalingRequired, Rect dirty, Rect surfaceInsets) {
		 ......
          mView.draw(canvas);
		 ......

    }

接着会调用到View的draw方法,其源码如下:

 @CallSuper
    public void draw(Canvas canvas) {
        final int privateFlags = mPrivateFlags;
        final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&
                (mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);
        mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;

        /*
         * Draw traversal performs several drawing steps which must be executed
         * in the appropriate order:
         *
         *      1. Draw the background
         *      2. If necessary, save the canvas' layers to prepare for fading
         *      3. Draw view's content
         *      4. Draw children
         *      5. If necessary, draw the fading edges and restore layers
         *      6. Draw decorations (scrollbars for instance)
         */

        // Step 1, draw the background, if needed
        int saveCount;

        if (!dirtyOpaque) {
            drawBackground(canvas);
        }

        // skip step 2 & 5 if possible (common case)
        final int viewFlags = mViewFlags;
        boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
        boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
        if (!verticalEdges && !horizontalEdges) {
            // Step 3, draw the content
            if (!dirtyOpaque) onDraw(canvas);

            // Step 4, draw the children
            dispatchDraw(canvas);

      
            // Step 6, draw decorations (foreground, scrollbars)
            onDrawForeground(canvas);

            // Step 7, draw the default focus highlight
            drawDefaultFocusHighlight(canvas);

            if (debugDraw()) {
                debugDrawFocus(canvas);
            }

            // we're done...
            return;
        }

      

        // Step 3, draw the content
        if (!dirtyOpaque) onDraw(canvas);

        // Step 4, draw the children
        dispatchDraw(canvas);

        // Step 5, draw the fade effect and restore layers
        final Paint p = scrollabilityCache.paint;
        final Matrix matrix = scrollabilityCache.matrix;
        final Shader fade = scrollabilityCache.shader;

     

        // Step 6, draw decorations (foreground, scrollbars)
        onDrawForeground(canvas);

    
    }

注释已经解释的很清楚了,主要过程遵循如下几步:

  1. 绘制背景 drawBackground(canvas);
  2. 绘制内容 onDraw(canvas);
  3. 绘制children dispatchDraw(canvas);
  4. 绘制装饰 onDrawForeground(canvas);

View绘制过程的传递是通过dispatchDraw()来实现的,它会遍历调用所有子元素的draw方法。

View的工作原理就学习到这里了 !!! 太难了~!慢慢学习!!

感谢:
https://blog.csdn.net/xfhy_/article/details/90270630#comments

参考:《Android 开发艺术探索》

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值