Android源码阅读分析:View体系(一)——体系概览及绘制流程

View体系(一)——体系概览及绘制流程


(注:源代码为android-8.1)

0. 前言

  本文主要分析Android的View体系及绘制流程。
  

1. View体系

  在一个窗口里的所有View会被统一管理在一个树状结构内,如图所示:
控件树状结构
  继承自ViewGroup的控件可以拥有子节点,而继承自View的控件一般只能作为叶节点。在实现自定义控件的时候需要注意到这一点。下图是部分系统自带控件的关系图。
系统自带控件继承关系
  
  

2. 代码解析

  ViewGroup添加一个View的具体执行内容。

(frameworks/base/core/java/android/view/ViewGroup.java)

public void addView(View child, int index, LayoutParams params) {
    ...
    if (child == null) {
        throw new IllegalArgumentException("Cannot add a null child view to a ViewGroup");
    }

    requestLayout();
    invalidate(true);
    addViewInner(child, index, params, false);
}

  分别看两种重绘方法:requestLayout方式和invalidate方式。

2.1 requestLayout方式

(frameworks/base/core/java/android/view/View.java)

@CallSuper
public void requestLayout() {
    ...
    if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == null) {
        ViewRootImpl viewRoot = getViewRootImpl();
        if (viewRoot != null && viewRoot.isInLayout()) {
            if (!viewRoot.requestLayoutDuringLayout(this)) {
                return;
            }
        }
        mAttachInfo.mViewRequestingLayout = this;
    }
    mPrivateFlags |= PFLAG_FORCE_LAYOUT;
    mPrivateFlags |= PFLAG_INVALIDATED;
    if (mParent != null && !mParent.isLayoutRequested()) {
        // 调用父节点的requestLayout方法
        mParent.requestLayout();
    }
    if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == this) {
        mAttachInfo.mViewRequestingLayout = null;
    }
}

  可以看出requestLayout方法所做的就是一路向上调用父节点的requestLayout方法。那么很明显,最终的执行内容要看根节点所重写的requestLayout方法内做了什么特殊的操作。文章《Android源码阅读分析:从Activity开始(二)——加载布局》中分析过,Activity加载布局,视图树的根节点是一个DecorView。但是我们查看DecorView的源码发现,它根本就没有重写requestLayout方法,这说明DecorView仍然不是处理requestLayout的最终根节点。那么我们就需要找到DecorViewmParent是什么。
  

2.1.1 寻找根节点

  DecorView继承自View,而ViewmParent则是在assignParent方法中进行设置的。经过查找,调用了View.assignParent方法的地方不多,唯一符合我们查找条件的则是在ViewRootImpl类的setView方法调用的。

(frameworks/base/core/java/android/view/ViewRootImpl.java)

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
    ...
    view.assignParent(this);
    ...
}

  ViewRootImpl.setView方法在WindowManagerGlobaladdView方法内被调用。

(frameworks/base/core/java/android/view/WindowManagerGlobal.java)

public void addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow) {
    ...
    root.setView(view, wparams, panelParentView);
    ...
}

  继续向上跟踪。

(frameworks/base/core/java/android/view/WindowManagerImpl.java)

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

  这个方法在ActivityThread类的handleResumeActivity方法中被调用了。

(frameworks/base/core/java/android/app/ActivityThread.java)

final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
    ...
    View decor = r.window.getDecorView();
    ViewManager wm = a.getWindowManager();
    ...
    wm.addView(decor, l);
    ...
}

  所以,DecorViewmParent其实是一个ViewRootImpl类的对象。也就是说,最终处理requestLayout的根节点。

扫描二维码关注公众号,回复: 11142018 查看本文章
(frameworks/base/core/java/android/view/ViewRootImpl.java)

@Override
public void requestLayout() {
    if (!mHandlingLayoutInLayoutRequest) {
        checkThread();
        mLayoutRequested = true;
        scheduleTraversals();
    }
}

  scheduleTraversals方法经过异步调用,最后会执行ViewRootImplperformTraversals方法。

(frameworks/base/core/java/android/view/ViewRootImpl.java)

private void performTraversals() {
    ...
    boolean layoutRequested = mLayoutRequested && (!mStopped || mReportNextDraw);
    if (layoutRequested) {
        ...
        windowSizeMayChange |= measureHierarchy(host, lp, res, desiredWindowWidth, desiredWindowHeight);
    }
    ...
    if (mFirst || windowShouldResize || insetsChanged || viewVisibilityChanged || params != null || mForceNextWindowRelayout) {
        ...
        relayoutResult = relayoutWindow(params, viewVisibility, insetsPending);
        ...
        if (!mStopped || mReportNextDraw) {
            if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth()
                    || mHeight != host.getMeasuredHeight() || contentInsetsChanged ||
                    updatedConfiguration) {
                ...
                // 测量host需求的尺寸
                performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
                ...
            }
        }
    }
    ...
    final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw);
    ...
    if (didLayout) {
        performLayout(lp, mWidth, mHeight);
        ...
    }
    ...
    boolean cancelDraw = mAttachInfo.mTreeObserver.dispatchOnPreDraw() || !isViewVisible;
    if (!cancelDraw && !newSurface) {
        ...
        performDraw();
    }
    ...
}

  performTraversals方法会依次调用performMeasureperformLayoutperformDraw方法。

(frameworks/base/core/java/android/view/ViewRootImpl.java)

private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
    ...
    mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    ...
}

private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth, int desiredWindowHeight) {
    ...
    final View host = mView;
    ...
    host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
    ...
}

private void performDraw() {
    ...
    draw(fullRedrawNeeded);
    ...
}

private void draw(boolean fullRedrawNeeded) {
    ...
    if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) {
        return;
    }
    ...
}

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

  这三个方法内的代码很多,这里只将最关键的代码提出来。可以看到,ViewRootImpl的这个三个方法,分别调用了Viewmeasurelayoutdraw方法。

2.1.2 measure相关

(frameworks/base/core/java/android/view/View.java)

// 该方法用于计算视图大小,父节点提供宽高参数的限制条件
// 实际的测量工作会在onMeasure方法中执行
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
    ...
    // 调整widthMeasureSpec和heightMeasureSpec参数
    ...
    final boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;
    ...
    final boolean needsLayout = specChanged && (sAlwaysRemeasureExactly || !isSpecExactly || !matchesSpecSize);
    if (forceLayout || needsLayout) {
        ...
        onMeasure(widthMeasureSpec, heightMeasureSpec);
        ...
    }
}

// View的子类需要重写该方法来计算其内容的精确有效的测量值
// 要注意的是,重写该方法需要调用setMeasuredDimension(int, int)方法来记录测量的宽高
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
            getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}

  measure方法用于计算视图大小,主要作用是提供父节点的限制条件,其内部会调用onMeasure方法,而onMeasure方法是实际进行测量的方法。View的子类(包括TextViewFrameLayout等framework自带的控件以及我们自定义的控件)都需要重写onMeasure方法,来测量精确有效的宽高值。
  这里我们分别用继承自ViewViewGroup的两个典型子类来做为示例:TextViewFrameLayout

(frameworks/base/core/java/android/widget/TextView.java)

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    int widthMode = MeasureSpec.getMode(widthMeasureSpec);
    int heightMode = MeasureSpec.getMode(heightMeasureSpec);
    int widthSize = MeasureSpec.getSize(widthMeasureSpec);
    int heightSize = MeasureSpec.getSize(heightMeasureSpec);

    int width;
    int height;
    ...
    // 计算宽度
    if (widthMode == MeasureSpec.EXACTLY) {
        // 父节点已经指定了该控件大小,直接使用
        width = widthSize;
    } else {
        ...
        // 根据参数设置计算TextView的宽度
        ...
    }
    ...
    // 高度的计算与宽度计算类似
    // 对于TextView来说,高度计算简单很多
    ...
    // 记录测量的宽高
    setMeasuredDimension(width, height);
}
(frameworks/base/core/java/android/widget/FrameLayout.java)

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    int count = getChildCount();
    ...
    int maxHeight = 0;
    int maxWidth = 0;
    int childState = 0;
    // 计算所有子节点的尺寸
    for (int i = 0; i < count; i++) {
        final View child = getChildAt(i);
        if (mMeasureAllChildren || child.getVisibility() != GONE) {
            // 该方法会调用子节点的measure方法
            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);
            ...
        }
    }
    // 根据参数调整宽高参数
    ...
    setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
                         resolveSizeAndState(maxHeight, heightMeasureSpec, childState << MEASURED_HEIGHT_STATE_SHIFT));
    ...
}

  对于继承自View的子类只需要对自身进行测量,而对于继承自ViewGroup的子类来说,则需要先测量所有子节点,然后在根据子节点的测量值和自身参数设置来测量自身。在实现自定义控件时,需要注意到这一点

2.1.3 layout相关

(frameworks/base/core/java/android/view/View.java)

@SuppressWarnings({"unchecked"})
public void layout(int l, int t, int r, int b) {
    ...
    onLayout(changed, l, t, r, b);
    ...
}

protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
}

  layout方法用于给控件极其子节点分配位置和尺寸。自定义控件或者系统自带控件都会重写onLayout方法来实际,如果是继承自ViewGroup类的控件,则还需要在onLayout方法内调用子节点的layout方法。

2.1.4 draw相关

(frameworks/base/core/java/android/view/View.java)

@CallSuper
public void draw(Canvas canvas) {
    ...
    final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE && (mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);
    ...
    if (!dirtyOpaque) {
        drawBackground(canvas);
    }
    ...
    if (!dirtyOpaque) onDraw(canvas);
    final int viewFlags = mViewFlags;
    boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
    boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
    if (!verticalEdges && !horizontalEdges) {
        if (!dirtyOpaque) onDraw(canvas);
        dispatchDraw(canvas);
        ...
    }
    ...
}

  draw方法用于在给定的Canvas上绘制控件。在该方法调用时,该控件的layout相关方法必须完全执行完毕。draw方法绘制控件,先通过onDraw方法进行实际绘制,再通过调用dispatchDraw方法绘制子节点。我们实现自定义控件时,需要重写onDraw方法。ViewdispatchDraw方法本身为空,而在ViewGroup类中重写了dispatchDraw方法。

(frameworks/base/core/java/android/view/ViewGroup.java)

@Override
protected void dispatchDraw(Canvas canvas) {
    ...
    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) {
            more |= drawChild(canvas, child, drawingTime);
        }
    }
    ...
}

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

2.1.5 调用流程

  通过requestLayout方式绘制,可以总结为一下流程。
  

Created with Raphaël 2.1.2 View measure() onMeasure() hasChildren child.measure() child.onMeasure() layout() onLayout() hasChildren child.layout() child.onLayout() draw() onDraw() hasChildren child.draw() child.onDraw() End yes no yes no yes no

2.2 invalidate方式

  invalidate方法也是常用的重绘界面的方法,在ViewGroupaddView方法内,调用requestLayout方法后也调用了invalidate方法。

(frameworks/base/core/java/android/view/View.java)

public void invalidate(boolean invalidateCache) {
    invalidateInternal(0, 0, mRight - mLeft, mBottom - mTop, invalidateCache, true);
}

void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache, boolean fullInvalidate) {
    ...
    if ((mPrivateFlags & (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)) == (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)
            || (invalidateCache && (mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == PFLAG_DRAWING_CACHE_VALID)
            || (mPrivateFlags & PFLAG_INVALIDATED) != PFLAG_INVALIDATED
            || (fullInvalidate && isOpaque() != mLastIsOpaque)) {
        ...
        // 将损毁的矩形发送到父节点
        final AttachInfo ai = mAttachInfo;
        final ViewParent p = mParent;
        if (p != null && ai != null && l < r && t < b) {
            final Rect damage = ai.mTmpInvalRect;
            damage.set(l, t, r, b);
            p.invalidateChild(this, damage);
        }
        ...
    }
}

  这里会调用ViewParentinvalidateChild方法。ViewGroup实现了ViewParent接口。来看一下ViewGroupinvalidateChild方法。

(frameworks/base/core/java/android/view/ViewGroup.java)

@Override
public final void invalidateChild(View child, final Rect dirty) {
    ...
    ViewParent parent = this;
    ...
    do {
        ...
        parent = parent.invalidateChildInParent(location, dirty);
        ...
    } while (parent != null);
    ...
}

  ViewGroupinvalidateChild方法内会调用其父节点的invalidateChildInParent方法。而ViewGroupinvalidateChildInParent方法内只是对一些参数进行设置调整。那么按照惯例,查看ViewRootImplinvalidateChildInParent重写方法。

(frameworks/base/core/java/android/view/ViewRootImpl.java)

@Override
public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
    ...
    if (dirty == null) {
        invalidate();
        return null;
    }
    ...
    invalidateRectOnScreen(dirty);
    return null;
}

  这里invalidateinvalidateRectOnScreen最后都会调用scheduleTraversals方法。根据上文的代码分析,这里最终会调用performTraversals方法。但是这里performTraversals方法只会调用performDraw方法,因为其mLayoutRequested变量此时为false,不会进入调用performLayout的逻辑。
  那么剩下的调用逻辑则和requestLayout方式的一样了。

Created with Raphaël 2.1.2 View draw() onDraw() hasChildren child.draw() child.onDraw() End yes no

3. 总结

  本文主要对控件体系进行概览,以及绘制流程的大致解析。后续文章中,会对控件体系进行详细的分析。

发布了21 篇原创文章 · 获赞 63 · 访问量 6万+

猜你喜欢

转载自blog.csdn.net/dongzhong1990/article/details/80484828
今日推荐