[Source code learning] UI drawing process

1. How View is added to the screen window

Before understanding how Views are added to screen windows, let's understand a few concepts

  • Window: is an abstract class that provides a set of APIs for drawing windows
  • PhoneWindow: is the only inherited implementation class of Window, which contains a DecorView object, which is the root View of all windows (Actvivty)
  • DecorView: It is the internal class of PhoneWindow, a subclass of FrameLayout, a functional modification of Framelayout (so called Decorxxx), and the root View of all application windows

Taking Activity as an example (AppCompatActivity is slightly different), our layout is to be loaded into the window by calling the setContentView(layoutResId)incoming layout resource id through the onCrete method, and going through the following three processes

  1. Create a top-level layout container DecorView
  2. Load base layout ViewGroup in top level layout
  3. Add setContentView(layoutResId) to FragmeLayout in base layout

The main implementation classes involved are Activityand PhoneWindow, and the main code implementation process is as follows

Activity.class

public void setContentView(@LayoutRes int layoutResID) {
    getWindow().setContentView(layoutResID);
    initWindowDecorActionBar();
}
复制代码

PhoneWindow.class

@Override
public void setContentView(int layoutResID) {
    if (mContentParent == null) {
        // step1: 初始化mContentParent
        installDecor();
    } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
        mContentParent.removeAllViews();
    }

    if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
        final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
                getContext());
        transitionTo(newScene);
    } else {
        //step6:把我们传入的layoutResID绘制成view,并作为mContentParent的子view
        mLayoutInflater.inflate(layoutResID, mContentParent);
    }
    ...
}

private void installDecor() {
    mForceDecorInstall = false;
    if (mDecor == null) {
        //step1: 创建一个DecorView作为Activity的跟布局
        mDecor = generateDecor(-1);
        ...
    } else {
        mDecor.setWindow(this);
    }
    if (mContentParent == null) {
        //step2: 调用generateLayout创建系统给的基础布局
        mContentParent = generateLayout(mDecor);
    }
    ...
}

protected ViewGroup generateLayout(DecorView decor) {
    ...
    int layoutResource;
    int features = getLocalFeatures();
    ...
    else {
        //step3:根据设置的主题指定一个基础布局,这里以R.layout.screen_simple为例
        layoutResource = R.layout.screen_simple;
    }

    mDecor.startChanging();
    //step4: 把screen_simple绘制成view并add到DecorView中
    mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
    //step5: creen_simple为LinearLayout,其中包含一个id=content的FrameLayout的子view
    //作为后续用来承载我们设置的xml布局的父view
    ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
    return contentParent;
}
复制代码

step1~6Pay attention to the key code implementation of the comments , and complete the loading of our resource layout into the window (DecorView). The loading process of the View is completed as above, but the coordinates, width and height of the View are not determined. Next, we will draw our View to determine the View's Various properties

Second, the drawing process of View

2.1, draw the entrance

ActivityThread.handlerResumeActivity()Call wm.addView WindowManagerthe implementation class of this wm isWindowManagerImpl

@Override
public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
        String reason) {
    final Activity a = r.activity;
    if (r.window == null && !a.mFinished && willBeVisible) {
        r.window = r.activity.getWindow();
        View decor = r.window.getDecorView();
        decor.setVisibility(View.INVISIBLE);
        //step1: 这里wm的实现类是WindowMamangerImpl
        ViewManager wm = a.getWindowManager();
          ...
        if (a.mVisibleFromClient) {
            if (!a.mWindowAdded) {
                a.mWindowAdded = true;
                //step2: 执行WindowManagerImpl.addView()
                wm.addView(decor, l);
            } else {
                a.onWindowAttributesChanged(l);
            }
        }
        ...
    } 
}

//关注a.getWindowManager(),调用的是Activity的getWindowManager(),而实际又是Window类个mWindowManager
WindowManager = mWindow.getWindowManager();

//而Window类的MWindowManager创建过程如下
public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
        boolean hardwareAccelerated) {
    ...
    if (wm == null) {
        wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
    }
    //返回的实现类型为WindowManagerImpl
    mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
}
复制代码

确定了WindowManager实际类型为WindowManagerImpl后,继续跟进addView方法,调用过程如下 WindowManagerImpl.addView(decroView,layoutParams)进而通过调用WindowManagerGlobal.addView()方法,并创建ViewRootImpl,调用ViewRootImpl.setView(decorView,layoutParams,parentView),进而调用ViewRootImpl的requestLayout()->sheduleTraversals()->doTraversal()->并最终调用performTraversals(),调用顺序用图形表示如下

55555.webp

重点关注最后的performTraversals()方法,在performTraversals()中会依次调用如下三个方法去完成View绘制的关键三步,测量,摆放和绘制

  • performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
  • performLayout(lp, mWidth, mHeight)、
  • performDraw();

2.2、MeasureSpec

private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
    if (mView == null) {
        return;
    }
    Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
    try {
        mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    } finally {
        Trace.traceEnd(Trace.TRACE_TAG_VIEW);
    }
}
复制代码

performMeasure()方法调用view的measure()方法。并传入父容器的宽高MeasureSpec作为入参

MeasureSpace是View的一个静态内部类,代表一个 32 位 int 值,高 2 位代表测量模式 SpecMode,低 30 位代表规格大小 SpecSize,MeasureSpec 通过把 SpecMode 和 SpecSize 打包成一个 int 值避免过多的对象内存分配

主要实现如下,用来保存View的测量模式(SpecMode)和大小(SpecSize),并定义了三种测量模式

public static class MeasureSpec {
    private static final int MODE_SHIFT = 30;
    private static final int MODE_MASK  = 0x3 << MODE_SHIFT;//11000000000000000000000000000000

    public static final int UNSPECIFIED = 0 << MODE_SHIFT;//00000000000000000000000000000000

    public static final int EXACTLY     = 1 << MODE_SHIFT;//01000000000000000000000000000000

    public static final int AT_MOST     = 2 << MODE_SHIFT;//10000000000000000000000000000000

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

    @MeasureSpecMode
    public static int getMode(int measureSpec) {
        return (measureSpec & MODE_MASK);
    }

    public static int getSize(int measureSpec) {
        return (measureSpec & ~MODE_MASK);
    }
}
复制代码

MeasureSpec定义的三种模式

  1. UNSPECIFIED:父容器对子View的大小不做约束,它的值为0左移30位(00000000000000000000000000000000)
  2. EXACTLY:父容器计算好了子View的具体宽高,子View的大小就是SpecSize,它的值为1左移30位(01000000000000000000000000000000)
  3. AT_MOST:父容器指定了一个可用大小,子View的大小不能超过这个大小,它的值为2左移30位(10000000000000000000000000000000)

通过makeMeasureSpec(int size,int mode)方法把size和mode组装到一个32位的int里面

(size & ~MODE_MASK) | (mode & MODE_MASK)
/*
其中MODE_MASK是0x3左移30位=11000000000000000000000000000000
(size & 00111111111111111111111111111111) | (mode & 11000000000000000000000000000000) 
size & 00111111111111111111111111111111 得到低30位
mode & 11000000000000000000000000000000 得到高2位
再把低30位和高两位取`与`操作,就完成了高 2 位代表测量模式 `SpecMode`,低 30 位代表规格大小 `SpecSize`
*/
复制代码

2.3、绘制三大步骤

2.3.1、performMeasure - 测量

再来看View的测量过程performMeasure

public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
    boolean optical = isLayoutModeOptical(this);
    if (optical != isLayoutModeOptical(mParent)) {
        Insets insets = getOpticalInsets();
        int oWidth  = insets.left + insets.right;
        int oHeight = insets.top  + insets.bottom;
        widthMeasureSpec  = MeasureSpec.adjust(widthMeasureSpec,  optical ? -oWidth  : oWidth);
        heightMeasureSpec = MeasureSpec.adjust(heightMeasureSpec, optical ? -oHeight : oHeight);
    }

    // Suppress sign extension for the low bytes
    long key = (long) widthMeasureSpec << 32 | (long) heightMeasureSpec & 0xffffffffL;
    if (mMeasureCache == null) mMeasureCache = new LongSparseLongArray(2);

    final boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;

    // Optimize layout by avoiding an extra EXACTLY pass when the view is
    // already measured as the correct size. In API 23 and below, this
    // extra pass is required to make LinearLayout re-distribute weight.
    final boolean specChanged = widthMeasureSpec != mOldWidthMeasureSpec
            || heightMeasureSpec != mOldHeightMeasureSpec;
    final boolean isSpecExactly = MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY
            && MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY;
    final boolean matchesSpecSize = getMeasuredWidth() == MeasureSpec.getSize(widthMeasureSpec)
            && getMeasuredHeight() == MeasureSpec.getSize(heightMeasureSpec);
    final boolean needsLayout = specChanged
            && (sAlwaysRemeasureExactly || !isSpecExactly || !matchesSpecSize);

    if (forceLayout || needsLayout) {
        // first clears the measured dimension flag
        mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;

        resolveRtlPropertiesIfNeeded();

        int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);
        if (cacheIndex < 0 || sIgnoreMeasureCache) {
            // measure ourselves, this should set the measured dimension flag back
            onMeasure(widthMeasureSpec, heightMeasureSpec);
            mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
        } else {
            long value = mMeasureCache.valueAt(cacheIndex);
            // Casting a long to int drops the high 32 bits, no mask needed
            setMeasuredDimensionRaw((int) (value >> 32), (int) value);
            mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
        }
        ...
    }

    mOldWidthMeasureSpec = widthMeasureSpec;
    mOldHeightMeasureSpec = heightMeasureSpec;

    mMeasureCache.put(key, ((long) mMeasuredWidth) << 32 |
            (long) mMeasuredHeight & 0xffffffffL); // suppress sign extension
}
复制代码

measure方法中会调用onMeasure(widthMeasureSpec, heightMeasureSpec)方法,

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

protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
    boolean optical = isLayoutModeOptical(this);
    if (optical != isLayoutModeOptical(mParent)) {
        Insets insets = getOpticalInsets();
        int opticalWidth  = insets.left + insets.right;
        int opticalHeight = insets.top  + insets.bottom;

        measuredWidth  += optical ? opticalWidth  : -opticalWidth;
        measuredHeight += optical ? opticalHeight : -opticalHeight;
    }
    setMeasuredDimensionRaw(measuredWidth, measuredHeight);
}

private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
    mMeasuredWidth = measuredWidth;
    mMeasuredHeight = measuredHeight;

    mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
}
复制代码

最终调用的setMeasuredDimensionRaw方法并确定mMeasuredWidthmMeasureHeight的值,也就是测量的过程目的就是为了确定宽高的值

再回到onMeasure方法,如果此时是ViewGroup,我们一般需要重写onMeasure方法,以FrameLayout为例

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    int count = getChildCount();

    final boolean measureMatchParentChildren =
            MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY ||
            MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY;
    mMatchParentChildren.clear();

    int maxHeight = 0;
    int maxWidth = 0;
    int childState = 0;
    //step 1: 遍历子View 
    for (int i = 0; i < count; i++) {
        final View child = getChildAt(i);
        if (mMeasureAllChildren || child.getVisibility() != GONE) {
            //step 2: 测量子View 
            measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
            maxWidth = Math.max(maxWidth,
                    child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
            maxHeight = Math.max(maxHeight,
                    child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
            childState = combineMeasuredStates(childState, child.getMeasuredState());
            if (measureMatchParentChildren) {
                if (lp.width == LayoutParams.MATCH_PARENT ||
                        lp.height == LayoutParams.MATCH_PARENT) {
                    mMatchParentChildren.add(child);
                }
            }
        }
    }

    //step 3:根据子view的测量结果,计算当前Framelayout的最终宽高 
    maxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground();
    maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground();

    // Check against our minimum height and width
    maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
    maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());

    // Check against our foreground's minimum height and width
    final Drawable drawable = getForeground();
    if (drawable != null) {
        maxHeight = Math.max(maxHeight, drawable.getMinimumHeight());
        maxWidth = Math.max(maxWidth, drawable.getMinimumWidth());
    }

    setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
            resolveSizeAndState(maxHeight, heightMeasureSpec,
                    childState << MEASURED_HEIGHT_STATE_SHIFT));
      ...
}
复制代码

在FrameLayout的onMeasure中,首选要遍历子View,通过measureChildWithMargins方法中再调用getChildMeasureSpec确定View的SpecMode个SpecSize

public abstract class ViewGroup extends View implements ViewParent, ViewManager {
    protected void measureChildWithMargins(View child,
            int parentWidthMeasureSpec, int widthUsed,
            int parentHeightMeasureSpec, int heightUsed) {
        final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
           //获取子view的MeasureSpec
        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);
        //step 4:把当前测量的子view的MeasureSpec作为入参,调用子View的measure方法,
        //递归调用,使得View树进入下一层级的测量
        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }
    
    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);
    }
}
复制代码

getChildMeasureSpec(int spec, int padding, int childDimension)实现如上

确定SpecMode和SpecSize的影响因素有父容器的MeasureSpec自身的LayoutParams,规则入下表所示

7788.webp

当获取到了子View的MeasureSpec后,把MeasureSpec作为入参继续调用子View的measure方法, 继续测量View树的下一层,进而完成整个View树的测量过程

2.3.2、performLayout - 摆放

performLayout方法会调用view.layout方法

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;
    //step 1: 通过setFrame方法确定View的摆放位置
    boolean changed = isLayoutModeOptical(mParent) ?
            setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);

    if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
        //step 2: 然后再调用layout方法,实现子view的摆放
        onLayout(changed, l, t, r, b);
        ...
    }
复制代码

layout方法中首先会调用setFrame()方法设定View的位置,也就是左上右下,确定了自身位置后再通过onMeasure确定子view的位置,我们在自定义ViewGrope时一般需要重写onLayout方法,根据我们ViewGroup的特性以确定子View改最终的摆放位置并调用子view.layout(l,t,r,b)进行摆放

2.3.3、performDraw - 绘制

performDraw -> draw(Canves fullRedrawNeeded) -> drawSoftware -> view.draw(canves) 调用流程如上,最终调用view的draw方法

public void draw(Canvas canvas) {
    final int privateFlags = mPrivateFlags;
    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;

    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
        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);
        return;
    }
   ...
}
复制代码

onDraw(canves)绘制方法共有6步

  1. 绘制背景
  2. 保存 Canvas 图层
  3. 绘制自身内容的内容
  4. 绘制子View (dispatchDraw)
  5. 绘制 Canvas 图层
  6. 绘制装饰(比如 foreground 和 scrollbar)

重点关注绘制的第三步和第四步

  • 第三步:调用了onDraw(canvas),如果我们是自定义View的话一般需要复写onDraw方法,在里面进行Canves自身内容的绘制

  • 第四步:调用了dispatchDraw(canvas),如果当前View是ViewGroup那么就会调用ViewGroup的dispatchDraw方法,遍历所有子View并调用子View的draw方法,完成绘制方法在View树的逐层执行

@Override
protected void dispatchDraw(Canvas canvas) {
    ...
    
    for (int i = 0; i < childrenCount; i++) {
        while (transientIndex >= 0 && mTransientIndices.get(transientIndex) == i) {
            final View transientChild = mTransientViews.get(transientIndex);
            if ((transientChild.mViewFlags & VISIBILITY_MASK) == VISIBLE ||
                    transientChild.getAnimation() != null) {
                    //
                more |= drawChild(canvas, transientChild, drawingTime);
            }
            transientIndex++;
            if (transientIndex >= transientCount) {
                transientIndex = -1;
            }
        }
    }
    ...
}

protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
    return child.draw(canvas, this, drawingTime);
}
复制代码

Guess you like

Origin juejin.im/post/7079629919705104398