1. Activity hierarchy

Activities do a lot of things. To understand decoupling and reuse, put the management of the appearance and behavior into the window. Let the Window be responsible for the creation and management of the View and the interaction with the viewRootImpl.

  • PhoneWindow:windowThe only implementation of.
  • DecorView:windowThe top – level view, including window decorations. Inherited fromFrameLayoutIs the real root of a view. And Window hold each other.
  • ContentParent:DecorViewTo place the window content view. So you could say yesDecorViewBy itself, you can also think of it asDecorViewIs used to hold children of the content view.

When in the Activity by the setContentView () to set up the view, layer upon layer transfer to PhoneWindow. The setContentView (), through addView () or inflate () added to the ContentParent.

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

Two, draw the starting point

  • DecorView is the topmost View, which is the root of the View tree, from which the entire View tree is drawn.

  • DecorView drawing is controlled by ViewRootImpl, ViewRootImpl performTraversals () has called the performMeasure (), peformLayout (), performDraw (), among them, DecorView (DecorView) : measure(); Layout (); draw();

    private void performTraversals(a) {... performMeasure(); . performLayout(); . performDraw(); }private void performMeasure(a) {... mView.measure(); }private void performLayout(a) {... mView.layout(); }private void performDraw(a) {... mView.draw(); }Copy the code
  • The starting point for ViewRootImpl work is when viewrooTimpl.setView () is called. The main thing viewrootimpl.setView () does is:

    1. Store the DecorView in ViewRootImpl.

    2. Call requestLayout() to start drawing.

    3. Use the ViewRootImpl as the parent in the DecorView to complete the binding.

    void setView(View view,...) {
        if (mView == null) { mView = view; . requestLayout(); . view.assignParent(this); }}Copy the code
  • The viewrootimpl.addView () call chain:

    • ActivityThread.handleResumeActivity()
    • WindowManagerImpl.addView()
    • WindowManagerGlobal.addView()Is created inViewRootImplAnd call theViewRootImpl.setView()

    According to this call chain, the drawing process does not start until after resume, so the width and height of the view have not been calculated before, so the width and height of the view cannot be read. In addition, the actions before resume are not necessarily drawn.

  • ViewRootImpl. RequestLayout () call, not immediately start drawing, but the callback binding to the next frame, at the appointed time began to callback performTraversals (), and began to draw. Also can see ViewRootImpl. RequestLayout () in the thread inspection, this explains before ViewRootImpl initialized, not thread safe examination, namely before initialization in other threads to change the view of data may not be an error. This is why onCreate() does not report an error when updating the view data on the main thread.

    public void requestLayout(a) {
        if(...). { checkThread(); mLayoutRequested =true; scheduleTraversals(); }}void scheduleTraversals(a) {
        if (...) {
            ...
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); }}Copy the code
  • Recursive calls to the parent View. RequestLayout (). The requestLayout (), eventually recursion to ViewRootImpl. RequestLayout (), to map, can only measure and layout process.

    public void requestLayout(a) {...if (...) {
            mParent.requestLayout();
        }
    }
    Copy the code
  • The invalidate () recursive calls to the parent. InvalidateChild (), eventually recursion to ViewRootImpl. InvalidateChild repaint (), will only go the draw process. Recursion passes the redraw position, only the position of the calling view will be redrawn.

    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); }}Copy the code
  • Viewgroup.addview () calls requestLayout() and invalidate() to redraw. The parent of the DecorView is not a ViewRootImpl, but the parent is not a ViewRootImpl, and the parent is not a ViewRootImpl. So it’s not going to start drawing.

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

3. Drawing process

The drawing process is divided into three steps: measure, layout and draw.

1. The measure process

  • During the measure process, widthMeasureSpec and heightMeasureSoec are passed in layers. MesureSpec is a static class within the View that encapsulates the layout requirements passed from the parent to the children. Each MeasureSpec represents the height and width requirements. 32-bit ints are used, with the high 2 bits representing the measurement mode and the low 30 bits representing the measurement size. There are three measurement modes:

    • USPECIFIED: 00, the parent layout does not impose any constraints on the children.
    • EXACTLY: 01, the parent layout already determines the size of the children, such as match_parent or XXXDP.
    • AT_MOSTAdaptive, where children size themselves within a range given by the parent layout.
  • MeasureSpec also wraps several methods to facilitate the creation and unpacking of 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) {... }}Copy the code
  • Decorview.measure () executes the view.measure () method. You can see that this is a final method. OnMeasure () is called when a forced refresh is marked (forceLayout) or the size is not measured correctly (needsLayout).

    public final void measure(int widthMeasureSpec, int heightMeasureSpec) {...if(forceLayout || needsLayout) { ... onMeasure(widthMeasureSpec, heightMeasureSpec); }}Copy the code
  • For a View that is not a ViewGroup, you only need to measure itself in onMeasure(), usually by calling view.onmeasure (), where the size is calculated, and setMeasureDimension() to store the measured width and height.

    GetDefaultSize () returns the maximum size required by the parent class for both AT_MOST and EXACTLY, that is, wrap_content and match_parent are equal to match_parent. You need to be careful to handle this if you are implementing a custom View directly from the 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;
    }
    Copy the code
  • Decorview.onmeasure () executes the framelayout.onMeasure () method. The main steps are:

    • First of all, we go through all the subviews, callViewGroup.meaureChildWithMargins()Measure the size of the subview and obtain the maximum width and height of the subview as the minimum width and height required by itself.
    • And then callresolveSizeAndState()Combined with the parent pass layout requirements, the final width and height are calculated.
    • After calculating its own width and height, if setmatch_parentIf there is more than one child view, the width and height of these views will be calculated again, because the width and height of these views are affected byFrameLayoutThe previous calculation results may not be correct.
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    
        for (int i = 0; i < count; i++) {
            final View child = getChildAt(i);
            if(...). { measureChildWithMargins();finalFrameLayout.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); }}Copy the code
  • ViewGroup. MeasureChildWithMargins (), there are two main work

    • Combining the layout requirements of the parent class with its own Settings computations need to be passed to the layout requirements of the children
    • Call the subviewmeasure()

    At this point, we have completed a complete recursion, and we call the measure() of the child view, and then continue to pass it down according to the type of view, and complete the calculation of the whole view tree.

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

2. The layout process

  • Decorview.layout() executes viewGroup.layout () and mainly calls the super method, which is view.layout().

    public final void layout(int l, int t, int r, int b) {
        if (...) {
            ...
            super.layout(l, t, r, b); }}Copy the code
  • View.onlayout () first calls setFrame() to determine whether the position of its parent View has changed, and if so, calls 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;
    }
    Copy the code
  • If a view is not a ViewGroup and does not contain a child view, call view.onLayout (), which is an empty method; Viewgroup.onlayout () is abstract and requires different layout managers to implement different layout methods.

  • DecorView executes to framelayout.onLayout (), calculates the location of the child view, and calls the child view’s layout() method.

    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); }}Copy the code

3. The draw process

  • Decorview overrides the draw() method, calls the superclass method view.draw (), and then draws the menu background.

    public void draw(Canvas canvas) {
        super.draw(canvas);
    
        if(mMenuBackground ! =null) { mMenuBackground.draw(canvas); }}Copy the code
  • View.draw(), the official annotation of the drawing step:

    1. Draw the background
    2. Save the layer and prepare to draw the gradient edge (shadow, etc.)
    3. Draw their own
    4. Draw the son view
    5. Draw the gradient edge and restore the layer
    6. Draw decorations (such as scrollbars)
  • Where 2 and 5 are executed only when needed, the main drawing processes are 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 childrendispatchDraw(canvas); .// Step 6, draw decorations (foreground, scrollbars)onDrawForeground(canvas); . }Copy the code
  • View.drawbackgroud (), if the background is not empty, drawable.draw () is finally called to complete the background drawing.

    private void drawBackground(Canvas canvas) {
        final Drawable background = mBackground;
        if (background == null) {
            return; }... background.draw(canvas); }Copy the code
  • DecorView also overwrites ondraw(), which also calls the parent method and then draws itself. View.ondraw() is an empty method. Each View has a different content and needs to implement its own drawing method, as in decorview.ondraw (). In which to draw the backup background, completed its own drawing.

    public void onDraw(Canvas c) {
        super.onDraw(c);
    
        mBackgroundFallback.draw(this, mContentRoot, c, mWindow.mContentParent,
                mStatusColorViewState.view, mNavigationColorViewState.view);
    }
    Copy the code
  • Decorview.dispatchdraw () executes viewGroup.dispatchDraw (), which iterates over child views and calls the drawChild() method. This method makes no sense for views that are not viewgroups and have no subviews of their own, so view.dispatchdraw () is an empty method.

    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);
    }
    Copy the code
  • Viewgroup.drawchild () will call the other draw() method in the View that has a Boolean return value (there are two draw() methods in the View), which will also call the draw() method that has no return value. The Boolean draw() method is only available for viewgroup.drawChild (). The main work of this method is rendering and hardware acceleration depending on the layer type (CPU or GPU).

    boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {...if(...). {... draw(canvas); }... }Copy the code
  • To complete the draw once recursion, and finally complete the whole tree drawing.