学习笔记-浅析Android绘制流程

1,001 阅读5分钟

一、 Activity层级结构

Activity要做的事情比较多,为了解耦以及复用,将外观和行为的管理放到window中,让window负责view的创建管理以及与viewRootImpl的交互。

img

  • PhoneWindowwindow的唯一实现。
  • DecorViewwindow的顶层视图,包含窗口装饰。继承自FrameLayout,是一个视图真实的根。和window互相持有对方。
  • ContentParentDecorView的子view,放置窗口内容视图。即可以认为是DecorView本身,也可以认为它是DecorView的用来存放内容视图的子级。

当在Activity中通过setContentView()设置视图的时候,会层层传递到PhoneWindow.setContentView()中,通过addView()或者inflate()添加到ContentParent中。

public void setContentView(View view, ViewGroup.LayoutParams params) {
    if (...) {
        ...
    } else {
        mContentParent.addView(view, params);
    }
    ...
}

二、 绘制起点

  • DecorView是最顶层的View,是视图树的的根,整个视图树的绘制从这里开始。

  • DecorView的绘制由ViewRootImpl控制,ViewRootImpl.performTraversals()先后调用了performMeasure()peformLayout()performDraw(),在其中,分别对应调用了DecorViewmeasure()layout()draw()方法,进行对应的绘制流程。

    private void performTraversals() {
        ...
        performMeasure();
        ...
        performLayout();
        ...
        performDraw();
    }
    
    private void performMeasure() {
        ...
        mView.measure();
    }
    
    private void performLayout() {
        ...
        mView.layout();
    }
    
    private void performDraw() {
        ...
        mView.draw();
    }
    
  • ViewRootImpl工作的起点是在ViewRootImpl.setView()被调用的时候。ViewRootImpl.setView()主要的工作是:

    1. DecorView存入ViewRootImpl

    2. 调用requestLayout()启动绘制。

    3. ViewRootImpl作为parent存入DecorView,完成相互的绑定。

    void setView(View view,...) {
        if (mView == null) {
            mView = view;
            ...
            requestLayout();
            ...
            view.assignParent(this);
        }
    }
    
  • ViewRootImpl.addView()调用链:

    • ActivityThread.handleResumeActivity()
    • WindowManagerImpl.addView()
    • WindowManagerGlobal.addView(),在其中创建ViewRootImpl并调用ViewRootImpl.setView()

    根据这个调用链可以知道,resume之后才开始进行绘制流程,所以之前view的宽高并没有计算出来,读取不到view的宽高。另外,在resume之前的操作并不一定真正的绘制出来。

  • ViewRootImpl.requestLayout()调用的时候,不是立即开始绘制,而是将回调绑定到下一帧,在指定的时刻开始回调到performTraversals(),开始绘制。另外可以看到ViewRootImpl.requestLayout()中才进行了线程检查,这就解释了在ViewRootImpl被初始化之前,不会进行线程安全的检查,即在初始化之前在其他线程对view的数据进行更改可能不会报错。这也是onCreate()中不在主线程更新view数据不会报错的原因。

    public void requestLayout() {
        if (...) {
            checkThread();
            mLayoutRequested = true;
            scheduleTraversals();
        }
    }
    
    
    void scheduleTraversals() {
        if (...) {
            ...
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
        }
    }
    
  • View.requestLayout()递归调用parent.requestLayout(),最终递归到ViewRootImpl.requestLayout()中,重新进行绘制,只会走measurelayout流程。

    public void requestLayout() {
        ...
        if (...) {
            mParent.requestLayout();
        }
    }
    
  • View.invalidate()递归调用parent.invalidateChild(),最终递归到ViewRootImpl.invalidateChild()中重新绘制,只会走draw流程。递归时传递了重绘的位置,只会对调用的view的位置进行重绘。

    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) {
        ...
        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);
        }
    }
    
  • ViewGroup.addView()中先后调用了requestLayout()invalidate(),重新进行绘制。前面说过在setContentView()的时候,是通过addView()进行添加,但是这个调用的时间在resume之前,这个时候ViewRootImpl还没有初始化,DecorViewparent还不是VIewRootImpl,所以并不会开始绘制。

    public void addView(View child, int index, ViewGroup.LayoutParams params) {
        ...
        requestLayout();
        invalidate(true);
        addViewInner(child, index, params, false);
    }
    

三、 绘制流程

绘制流程分成三个步骤:measurelayoutdraw

1. measure流程

  • measure过程中,会层层传递widthMeasureSpecheightMeasureSoecMesureSpecView内的一个静态类,封装了父级传递到子级的布局要求,每个MeasureSpec表示了对高度宽度宽度的要求。使用32位int来表示,高2位表示测量模式,低30位表示测量尺寸。测量模式有三个:

    • USPECIFIED:00,父布局不对子级施加任何约束。
    • EXACTLY:01,父布局已经确定子级的大小,比如match_parent或者xxxdp。
    • AT_MOST:10,自适应,子级在父布局给定的范围内确定自己的大小。
  • MeasureSpec中还封装了几个方法,方便对于MeasureSpec的创建以及拆解。

  • public static class MeasureSpec {
        public static int makeMeasureSpec(int size, int mode) {
            ...
            return size + mode;
        }
    
        public static int getMode(int measureSpec) {
            ...
        }
    
        public static int getSize(int measureSpec) {
            ...
        }
    }
    
  • DecorView.measure()执行的是View.measure()方法,可以看到这是个final方法,也就是说所有view的measure都是走的这里,在标志了强制刷新(forceLayout)或者尺寸并未被正确测量(needsLayout)的情况下,会调用onMeasure()进行测量。

    public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
        ...
        if (forceLayout || needsLayout) {
            ...
            onMeasure(widthMeasureSpec, heightMeasureSpec);
        }
    }
    
  • 对于不是ViewGroupView,在onMeasure()中只需要测量自身即可,一般会调用View.onMeasure(),在其中计算大小,并调用setMeasureDimension()储存测量的宽高。

    在getDefaultSize()计算宽高的时候,对于AT_MOST和EXACTLY都返回父类要求的最大大小,即wrap_contentmatch_parent取的计算结果都相当于match_parent,如果直接继承View实现自定义view的时候,需要注意处理这种情况。

    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 = View.MeasureSpec.getMode(measureSpec);
        int specSize = View.MeasureSpec.getSize(measureSpec);
    
        switch (specMode) {
            case View.MeasureSpec.UNSPECIFIED:
                result = size;
                break;
            case View.MeasureSpec.AT_MOST:
            case View.MeasureSpec.EXACTLY:
                result = specSize;
                break;
        }
        return result;
    }
    
  • Decorview.onMeasure()执行的是FrameLayout.onMeasure()方法。主要的步骤是:

    • 首先遍历所有子view,调用ViewGroup.meaureChildWithMargins()测量子view大小,并取得子view中最大的宽高,作为自身可实现要求的最小宽高。
    • 而后调用resolveSizeAndState()与父级传递的布局要求综合计算获得最终宽高。
    • 计算出自身的宽高之后,如果设置match_parent的子view超过一个,会重新对这些view进行计算宽高,因为这些view宽高受到FrameLayout的宽高的影响,之前的计算结果可能并不正确。
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    
        for (int i = 0; i < count; i++) {
            final View child = getChildAt(i);
            if (...) {
                measureChildWithMargins();
                final FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) child.getLayoutParams();
                maxWidth = Math.max(maxWidth,
                        child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
                maxHeight = Math.max(maxHeight,
                        child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
                ...
                if (lp.width == FrameLayout.LayoutParams.MATCH_PARENT ||
                        lp.height == FrameLayout.LayoutParams.MATCH_PARENT) {
                    mMatchParentChildren.add(child);
                }
    
            }
        }
        ...
        setMeasuredDimension(resolveSizeAndState(maxWidth, ...),
                resolveSizeAndState(maxHeight, ...));
        
        count = mMatchParentChildren.size();
        if (count > 1) {
            ...
            child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
        }
    }
    
  • ViewGroup.measureChildWithMargins(),主要有两个工作

    • 结合父类的布局要求以及自身的设置计算需要传递到子级的布局要求
    • 调用子view的measure()

    到这里就完成了一次完整的递归,又调用到了子view的measure(),之后再继续根据view类型继续向下传递,完成整个view树的计算。

    protected void measureChildWithMargins(...) {
        ...
        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }
    

2. layout流程

  • Decorview.layout()执行的是ViewGroup.layout(),主要调用的是super的方法,也就是View.layout()

    public final void layout(int l, int t, int r, int b) {
        if (...) {
            ...
            super.layout(l, t, r, b);
        }
    }
    
  • View.onLayout()首先调用setFrame(),判断对其父view的位置是否发生改变,根据是否发生改变调用onLayout()

    public void layout(int l, int t, int r, int b) {
        ...
        boolean changed = isLayoutModeOptical(mParent) ?
                setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
    
        if (changed || ...) {
            onLayout(changed, l, t, r, b);
            ...
        }
    }
    
    private boolean setOpticalFrame(int left, int top, int right, int bottom) {
        ...
        return setFrame(
                left   + parentInsets.left - childInsets.left,
                top    + parentInsets.top  - childInsets.top,
                right  + parentInsets.left + childInsets.right,
                bottom + parentInsets.top  + childInsets.bottom);
    }
    
    protected boolean setFrame(int left, int top, int right, int bottom) {
        boolean changed = false;
    
        if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) {
            changed = true;
            ...
        }
        return changed;
    }
    
  • onLayout()主要完成对子view的重新布局,对于不是ViewGroupView不包含子View,调用View.onLayout(),是个空方法;而ViewGroup.onLayout()abstract,需要不同的布局管理器实现不同的布局方法。

  • DecorView会执行到FrameLayout.onlayout(),计算出子view的位置信息,并调用子view的layout()方法。

    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        layoutChildren(left, top, right, bottom, false /* no force left gravity */);
    }
    
    void layoutChildren(int left, int top, int right, int bottom, boolean forceLeftGravity) {
        ...
        for (int i = 0; i < getChildCount(); i++) {
            ...
            switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
                case Gravity.CENTER_HORIZONTAL:
                    childLeft = parentLeft + (parentRight - parentLeft - width) / 2 +
                            lp.leftMargin - lp.rightMargin;
                    break;
                case Gravity.RIGHT:
                    if (!forceLeftGravity) {
                        childLeft = parentRight - width - lp.rightMargin;
                        break;
                    }
                case Gravity.LEFT:
                default:
                    childLeft = parentLeft + lp.leftMargin;
            }
    
            switch (verticalGravity) {
                case Gravity.TOP:
                    childTop = parentTop + lp.topMargin;
                    break;
                case Gravity.CENTER_VERTICAL:
                    childTop = parentTop + (parentBottom - parentTop - height) / 2 +
                            lp.topMargin - lp.bottomMargin;
                    break;
                case Gravity.BOTTOM:
                    childTop = parentBottom - height - lp.bottomMargin;
                    break;
                default:
                    childTop = parentTop + lp.topMargin;
            }
    
            child.layout(childLeft, childTop, childLeft + width, childTop + height);
            
        }
    }
    

3. draw流程

  • Decorview重写了draw()方法,调用父类方法,也就是View.draw(),之后做了菜单背景的绘制。

    public void draw(Canvas canvas) {
        super.draw(canvas);
    
        if (mMenuBackground != null) {
            mMenuBackground.draw(canvas);
        }
    }
    
  • View.draw(),官方注释了绘制步骤:

    1. 绘制背景
    2. 保存图层,准备绘制渐变边缘(阴影等效果)
    3. 绘制自身
    4. 绘制子view
    5. 绘制渐变边缘,恢复图层
    6. 绘制装饰(类如滚动条)
  • 其中2和5只在需要的情况下才会执行,主要的绘制流程是1,3,4。

  • public void draw(Canvas canvas) {
        ...
        // Step 1, draw the background, if needed
        if (!dirtyOpaque) {
            drawBackground(canvas);
        }
    		...
        // Step 3, draw the content
        if (!dirtyOpaque) onDraw(canvas);
    		...
        // Step 4, draw the children
        dispatchDraw(canvas);
    		...
        // Step 6, draw decorations (foreground, scrollbars)
        onDrawForeground(canvas);
        ...
    }
    
  • View.drawBackgroud(),背景不为空的情况下,最终调用Drawable.draw()完成背景绘制。

    private void drawBackground(Canvas canvas) {
        final Drawable background = mBackground;
        if (background == null) {
            return;
        }
        ...
        background.draw(canvas);
    }
    
  • DecorView也重写了ondraw(),在其中也是先调用父类方法,然后绘制了自身,View.onDraw()是个空方法,每个view内容各不相同,需要对应的去实现各自的绘制方法,就比如DecorView.ondraw(),在其中绘制后备背景,完成了自身的绘制。

    public void onDraw(Canvas c) {
        super.onDraw(c);
    
        mBackgroundFallback.draw(this, mContentRoot, c, mWindow.mContentParent,
                mStatusColorViewState.view, mNavigationColorViewState.view);
    }
    
  • DecorView.dispatchDraw()执行的是ViewGroup.dispatchDraw(),在其中遍历子view并调用drawChild()方法。对于不是ViewGroupView来说,本身就没有子view,这个方法就没有什么意义,所以View.dispatchDraw()是个空方法。

    protected void dispatchDraw(Canvas canvas) {
        ...
        for (int i = 0; i < childrenCount; i++) {
            ...
            if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || ...) {
                more |= drawChild(canvas, child, drawingTime);
            }
        }
        ...
    }
    
    protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
        return child.draw(canvas, this, drawingTime);
    }
    
  • ViewGroup.drawChild()会调用到View中另一个有boolean返回值draw()方法(View中有两个draw()方法),其中还是会调用到没有返回值的draw()方法。官方注释解释,这个有booleandraw()方法,只是提供给ViewGroup.drawChild()使用,这个方法的主要工作是根据图层类型(CPU or GPU)进行渲染和硬件加速的地方。

    boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
        ...
        if (...) {
            ...
            draw(canvas);
        }
        ...
    }
    
  • 到这里完成了draw一次递归,最终完成整个树的绘制。