Android Review Tour - View's Workflow 1

Keywords: Windows Android

This article mainly reviews the original workflow of view in Android. The workflow of view includes three main processes: measure, layout and draw. Before entering the theme, we should understand several concepts in order to better understand the three workflows of view.

Understanding ViewRoot and DecorView
ViewRoot corresponds to the ViewRootImpl class and implements the ViewParent interface. It is the bridge connecting Windows Manager and DecorView. The implementation class of Windows Manager is the Windows Manager Impl class.

public final class WindowManagerImpl implements WindowManager {
    private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
    private final Context mContext;
    private final Window mParentWindow;

    private IBinder mDefaultToken;

Among them, Windows Manager Global is equivalent to a proxy class. The implementation methods in Windows Manager Impl are all implemented through Windows Manager Global. Let's take a look at the addView() method in Windows Manager Impl:

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

You can see that it is really implemented through the addView() method of Windows Manager Global. Enter the addView() method of Windows Manager Global:

public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {
      ...
        final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
        ViewRootImpl root;
        View panelParentView = null;

        synchronized (mLock) {
            int index = findViewLocked(view, false);
            if (index >= 0) {
                if (mDyingViews.contains(view)) {
                    // Don't wait for MSG_DIE to make it's way through root's queue.
                    mRoots.get(index).doDie();
                } else {
                    throw new IllegalStateException("View " + view
                            + " has already been added to the window manager.");
                }
                // The previous removeView() had not completed executing. Now it has.
            }

            // If this is a panel window, then find the window it is being
            // attached to for future reference.
            if (wparams.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW &&
                    wparams.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
                final int count = mViews.size();
                for (int i = 0; i < count; i++) {
                    if (mRoots.get(i).mWindow.asBinder() == wparams.token) {
                        panelParentView = mViews.get(i);
                    }
                }
            }
            //Instantiate ViewRootImpl
            root = new ViewRootImpl(view.getContext(), display);

            view.setLayoutParams(wparams);

            mViews.add(view);
            mRoots.add(root);
            mParams.add(wparams);

            // do this last because it fires off messages to start doing things
            try {
                //Connect Windows Manager and DecorView
                root.setView(view, wparams, panelParentView);
            } catch (RuntimeException e) {
                // BadTokenException or InvalidDisplayException, clean up.
                if (index >= 0) {
                    removeViewLocked(index, true);
                }
                throw e;
            }
        }
    }

From the code annotated above, you can see that ViewRootImpl is a bridge connecting Windows Manager and DecorView.

DecorView is a subclass of FrameLayout, which can be considered as the root node View of the Android View tree. DecorView, as the top-level View, usually contains a vertical line Layout, in which there are two parts (the specific situation is related to Android version and the main body), the top is the title bar, and the bottom is the content bar. In Activity, the layout file set by setContentView is actually added to the content bar, and the id of the content bar is content. In code, the layout corresponding to content can be obtained by ViewGroup content = (ViewGroup)findViewById(R.android.id.content).
The structure of DecorView:

Drawing process of View
The drawing process of View begins with the performance Traversals () method of ViewRootImpl. It can finally draw the view through three processes: measure, layout and draw. Among them, measure measures the width and height of the view, layout determines the position of the view in the parent container, and draw is responsible for drawing the view on the screen.
Drawing flow chart of view:

PerfmTraversals () method source code is relatively long, it is not posted, interested to see, from the above flow chart can be seen that performTraversals will call performMeasure (child Width MeasureSpec, child HeightMeasureSpec); performance Layout (lp, mWidth, mHeight); mDraw (); three methods, the three top-level view s of the three methods, in the performing Width MeasureSpec (child HeightMeasureSpec); performance Layout (mWidth, mDraw). Measure () calls the mView.measure() method, and mView.measure() calls onMeasure() method to measure all the child elements. At this time, the measurement process is passed from the parent container to the child elements, thus completing the sequential measurement process. The performance Layout () and performance draw () processes are similar to the performance measure (), the only difference is that the performance draw () process is on the drawer side. In the method, dispatch draw (canvas) is used to complete the drawing.

Understanding MeasureSpec
MeasureSpec is a 32-bit int value, high 2-bit stands for SpecMode, low 30-bit stands for SpecSize, SpecSize refers to the measurement mode, SpecSize refers to the size of a certain measurement mode.
Some constants within MeasureSpec:

        private static final int MODE_SHIFT = 30;
        private static final int MODE_MASK  = 0x3 << MODE_SHIFT;      
        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(int size,int mode) {
            if (sUseBrokenMakeMeasureSpec) {
                return size + mode;
            } else {
                return (size & ~MODE_MASK) | (mode & MODE_MASK);
            }
        }
public static int getMode(int measureSpec) {
            //noinspection ResourceType
            return (measureSpec & MODE_MASK);
        }

 public static int getSize(int measureSpec) {
            return (measureSpec & ~MODE_MASK);
        }

MesureSpec packages SpecMode and SpecSize into an int value. A group of SpecModes and SpecSize can also be packaged into a measureSpec to get the corresponding value through getMode and getSize.
SpecMode has three types:

  1. UNSPECIFIED
    The parent container does not have any restrictions on view, how big to give;
  2. EXACTLY
    The parent container has detected the exact size required for view, which is the value specified by SpecSize, and corresponds to match_parent in LayoutParams and the specific numerical value.
  3. AT_MOST
    The parent container specifies an available size, SpecSize, which the view cannot exceed, corresponding to wrap - content in layoutParams.

What does measureSpec have to do with Layout Params? When we set LayoutParams for view, the system will convert LayoutParams into corresponding measureSpec under the constraint of parent container.
For DecorView, the method of measure Hierarchy in ViewRootImpl is used to create measure Spec. The desired Windows Width is the width of the screen and the desired Windows Height is the height of the screen.

 childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);
            childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
            performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);

Enter the getRootMeasureSpec method:

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

This method calculates the corresponding measureSpec according to DecorView's own Layout Params.
For the measurements process of ordinary view, which is passed down by viewgroup, first look at the measureChildWithMargins method of viewgroup:

 protected void measureChildWithMargins(View child,
            int parentWidthMeasureSpec, int widthUsed,
            int parentHeightMeasureSpec, int heightUsed) {
        final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();

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

        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }

The measure ChildWithMargins method calculates the measure Spec of the child element through the getChild MeasureSpec method and then measures the child element. From the code, it can be seen that the measure Spec of the child element is related to the Massure Spec of the parent container and its own LayoutParams. In addition, it is also related to the margin and padding of view. Next, enter the getChildSpec Measure method:

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

The main function of this method is to determine the MeasureSpec of the child element based on MesureSpec of the parent container and LayoutParams of the view itself. The padding in the parameter refers to the size of the parent container occupied.

Posted by rUmX on Wed, 03 Apr 2019 10:33:32 -0700