Android View工作原理

    xiaoxiao2022-07-03  218

    前言

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

    1.一些必要的基本概念

    1.1 ViewRoot和DecorView

    ViewRoot对应于ViewRootImpl类,连接WindowManager和DecorView的纽带;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类:

    EXACTLY:match_parent和具体数值,父容器已经检测出View的精确大小AT_MOST:父容器制定一个可用大小,View的大小不能大于它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 <size, mode> 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过程

    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。

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

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

    绘制背景 drawBackground(canvas);绘制内容 onDraw(canvas);绘制children dispatchDraw(canvas);绘制装饰 onDrawForeground(canvas);

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

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

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

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

    最新回复(0)