A bad pen is better than a good memory. Taking notes in your life is not only convenient for yourself, but also for others.

(Most of the source code below is from API 28)

1. Knowledge premise of View

View drawing is done from top to bottom: DecorView–>ViewGroup (–>ViewGroup) –>View, so let’s look at the DecorView before we learn how to draw a View.

1.1 DecorView view structure

In Android, Activity exists as the carrier of an application, representing a complete user interface and providing a window to draw various views. Each activity corresponds to a window, which is an instance of a PhoneWindow. The layout of a PhoneWindow is DecirView, which is a FrameLayout. The other part is the ContentParent, which is the layout of the activity in the setContentView.

1.2 Look at the DecorView from the source

When an activity starts, it executes the setContentView method in onCreate:

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
Copy the code

Use setContentView as a starting point to analyze the relationship between Activity, PhoneWindow, DecorView, ActionBar and ContentParent.

Enter the setContentView

    /**
     * Set the activity content from a layout resource.  The resource will be
     * inflated, adding all top-level views to the activity.
     *
     * @param layoutResID Resource ID to be inflated.
     *
     * @see #setContentView(android.view.View)
     * @see #setContentView(android.view.View, android.view.ViewGroup.LayoutParams)
     */
    public void setContentView(@LayoutRes int layoutResID) {
        getWindow().setContentView(layoutResID);
        initWindowDecorActionBar();
    }
Copy the code

Enter the activity setContentView and find that the setContentView(layoutResID) of getWindow() is called.

Continue to see getWindow ()

    /**
     * Retrieve the current {@link android.view.Window} for the activity.
     * This can be used to directly access parts of the Window API that
     * are not available through Activity/Screen.
     *
     * @return Window The current window, or null if the activity is not
     *         visual.
     */
    public Window getWindow() {
        return mWindow;
    }
Copy the code
final void attach(... Omit the code here) {attachBaseContext(context); mFragments.attachHost(null /*parent*/); mWindow = new PhoneWindow(this, window, activityConfigCallback); mWindow.setWindowControllerCallback(this); . Omit the code}Copy the code

SetContentView getWindow() is a PhoneWindow. As mentioned above, each activity corresponds to a window, which is an instance of PhoneWindow.

Go ahead and look inside PhoneWindow:

    // This is the top-level view of the window, containing the window decor.
    private DecorView mDecor;
Copy the code

PhoneWindow has a DecorView object, mDecor, and look at the setContentView method

      @Override
    public void setContentView(int layoutResID) {
        // Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
        // decor, when theme attributes and the like are crystalized. Do not check the feature
        // before this happens.
        if (mContentParent == null) {
            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 {
            mLayoutInflater.inflate(layoutResID, mContentParent);
        }
        mContentParent.requestApplyInsets();
        final Callback cb = getCallback();
        if(cb ! = null && ! isDestroyed()) { cb.onContentChanged(); } mContentParentExplicitlySet =true;
    }
Copy the code

MContentParent should be null for the first time.

    private void installDecor() {
        mForceDecorInstall = false;
        if (mDecor == null) {
            mDecor = generateDecor(-1);
            mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
            mDecor.setIsRootNamespace(true);
            if(! mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures ! = 0) { mDecor.postOnAnimation(mInvalidatePanelMenuRunnable); }}else {
            mDecor.setWindow(this);
        }
        if(mContentParent == null) { mContentParent = generateLayout(mDecor); . Omit the code}}Copy the code

In installDecor, mDecor=generateDecor(-1), let’s look at how the DecorView is generated:

    protected DecorView generateDecor(int featureId) {
        // System process doesn't have application context and in that case we need to directly use // the context we have. Otherwise we want the application context, so we don't cling to the
        // activity.
        Context context;
        if (mUseDecorContext) {
            Context applicationContext = getContext().getApplicationContext();
            if (applicationContext == null) {
                context = getContext();
            } else {
                context = new DecorContext(applicationContext, getContext());
                if(mTheme ! = -1) { context.setTheme(mTheme); }}}else {
            context = getContext();
        }
        return new DecorView(context, featureId, this, getAttributes());
    }
Copy the code

Inside is a DecorView that is directly new.

The relationship between PhoneWindow and DecorView becomes clear here

How does a DecorView relate to the ActionBar and ContentParent?

Keep looking back at the installDecor() method for source code:

    private void installDecor() {
        mForceDecorInstall = false;
        if (mDecor == null) {
            mDecor = generateDecor(-1);
            mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
            mDecor.setIsRootNamespace(true);
            if(! mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures ! = 0) { mDecor.postOnAnimation(mInvalidatePanelMenuRunnable); }}else {
            mDecor.setWindow(this);
        }
        if(mContentParent == null) { mContentParent = generateLayout(mDecor); . Omit the code}}Copy the code

MContentParent is generated by generateLayout(mDecor).

protected ViewGroup generateLayout(DecorView decor) { // Apply data from current theme. TypedArray a = getWindowStyle();  . I omit the code int layoutResource; int features = getLocalFeatures(); // System.out.println("Features: 0x" + Integer.toHexString(features));
        if((features & (1 << FEATURE_SWIPE_TO_DISMISS)) ! = 0) {... Code omitted here}else {
            // Embedded, so no decoration is needed.
            layoutResource = R.layout.screen_simple;
            // System.out.println("Simple!"); } mDecor.startChanging(); mDecor.onResourcesLoaded(mLayoutInflater, layoutResource); ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT); . Omit code herereturn contentParent;
    }
Copy the code
    /**
     * The ID that the main layout in the XML layout file should have.
     */
    public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content;
Copy the code

The generateLayout method has a lot of code initialized for different configurations, so I’ve omitted some other code

In the generateLayout method, mDecor loads the R.layout.screen_simple layout

    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true"
    android:orientation="vertical">
    <ViewStub android:id="@+id/action_mode_bar_stub"
              android:inflatedId="@+id/action_mode_bar"
              android:layout="@layout/action_mode_bar"
              android:layout_width="match_parent"
              android:layout_height="wrap_content"
              android:theme="? attr/actionBarTheme" />
    <FrameLayout
         android:id="@android:id/content"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:foregroundInsidePadding="false"
         android:foregroundGravity="fill_horizontal|top"
         android:foreground="? android:attr/windowContentOverlay" />
</LinearLayout>
Copy the code

Screen_simple is a vertical linear layout. The top ViewStub is the appBar of the APP, and the bottom FrameLayout id is Content! The XML page that the activity loads is loaded into this layout

Take a look at mDecor. OnResourcesLoaded (mLayoutInflater, layoutResource)

void onResourcesLoaded(LayoutInflater inflater, int layoutResource) { ... Omit the code here mDecorCaptionView = createDecorCaptionView(inflater); final View root = inflater.inflate(layoutResource, null);if(mDecorCaptionView ! = null) {if (mDecorCaptionView.getParent() == null) {
                addView(mDecorCaptionView,
                        new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
            }
            mDecorCaptionView.addView(root,
                    new ViewGroup.MarginLayoutParams(MATCH_PARENT, MATCH_PARENT));
        } else {

            // Put it below the color views.
            addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
        }
        mContentRoot = (ViewGroup) root;
        initializeElevation();
    }
Copy the code

From the above method, the root View stands for r.layout. screen_simple. Then the DecorView calls addView to load root into the DecorView

The ActionBar is not initialized, but the ContentParent is initialized.

Go back to the original source code for the Activity:

/**
     * Set the activity content from a layout resource.  The resource will be
     * inflated, adding all top-level views to the activity.
     *
     * @param layoutResID Resource ID to be inflated.
     *
     * @see #setContentView(android.view.View)
     * @see #setContentView(android.view.View, android.view.ViewGroup.LayoutParams)
     */
    public void setContentView(@LayoutRes int layoutResID) {
        getWindow().setContentView(layoutResID);
        initWindowDecorActionBar();
    }
Copy the code

InitWindowDecorActionBar () :

      /**
     * Creates a new ActionBar, locates the inflated ActionBarView,
     * initializes the ActionBar with the view, and sets mActionBar.
     */
    private void initWindowDecorActionBar() {
        Window window = getWindow();

        // Initializing the window decor can change window feature flags.
        // Make sure that we have the correct set before performing the test below.
        window.getDecorView();

        if(isChild() || ! window.hasFeature(Window.FEATURE_ACTION_BAR) || mActionBar ! = null) {return;
        }

        mActionBar = new WindowDecorActionBar(this);
        mActionBar.setDefaultDisplayHomeAsUpEnabled(mEnableDefaultActionBarUp);

        mWindow.setDefaultIcon(mActivityInfo.getIconResource());
        mWindow.setDefaultLogo(mActivityInfo.getLogoResource());
    }
Copy the code
    @RestrictTo({Scope.LIBRARY_GROUP})
    public WindowDecorActionBar(View layout) {
        assert layout.isInEditMode();

        this.init(layout);
    }
Copy the code
      private void init(View decor) {
        this.mOverlayLayout = (ActionBarOverlayLayout)decor.findViewById(id.decor_content_parent);
        if(this.mOverlayLayout ! = null) { this.mOverlayLayout.setActionBarVisibilityCallback(this); } this.mDecorToolbar = this.getDecorToolbar(decor.findViewById(id.action_bar)); this.mContextView = (ActionBarContextView)decor.findViewById(id.action_context_bar); this.mContainerView = (ActionBarContainer)decor.findViewById(id.action_bar_container);if(this.mDecorToolbar ! = null && this.mContextView ! = null && this.mContainerView ! = null) { this.mContext = this.mDecorToolbar.getContext(); int current = this.mDecorToolbar.getDisplayOptions(); boolean homeAsUp = (current & 4) ! = 0;if (homeAsUp) {
                this.mDisplayHomeAsUpSet = true;
            }

            ActionBarPolicy abp = ActionBarPolicy.get(this.mContext);
            this.setHomeButtonEnabled(abp.enableHomeButtonByDefault() || homeAsUp);
            this.setHasEmbeddedTabs(abp.hasEmbeddedTabs());
            TypedArray a = this.mContext.obtainStyledAttributes((AttributeSet)null, styleable.ActionBar, attr.actionBarStyle, 0);
            if (a.getBoolean(styleable.ActionBar_hideOnContentScroll, false)) {
                this.setHideOnContentScrollEnabled(true);
            }

            int elevation = a.getDimensionPixelSize(styleable.ActionBar_elevation, 0);
            if(elevation ! = 0) { this.setElevation((float)elevation);
            }

            a.recycle();
        } else {
            throw new IllegalStateException(this.getClass().getSimpleName() + " can only be used " + "with a compatible window decor layout"); }}Copy the code

The ActionBar and ContentParent are not added to the DecorView, but are inherent in the DecorView.

For an activity with an ActionBar, the default layout of the DecorView is screen_action_bar. XML, which contains both the ActionBar and ContentParent. The default decorView layout is selected based on the parameters of the Activity, such as screen_simple.xml

The determination logic for selecting the default layout of a DecorView is done by calling generateLayout in the installDecor method.

The relationship between the DecorView and the ContentParent and ActionBar becomes clear

1.3 DecorView establishes the association with viewRootImpl

Start the Activity in the ActivityThread’s handleLaunchActivity, and when the onCreate() method completes, so does the DecorView creation action described above. The handleLaunchActivity method continues to call the handleResumeActivity method of the ActivityThread.

  @Override
    public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
            String reason) {
        ...

        // TODO Push resumeArgs into the activity forconsideration final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason); .if(r.window == null && ! a.mFinished && willBeVisible) { r.window = r.activity.getWindow(); View decor = r.window.getDecorView(); decor.setVisibility(View.INVISIBLE); ViewManager wm = a.getWindowManager(); WindowManager.LayoutParams l = r.window.getAttributes(); a.mDecor = decor; l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION; l.softInputMode |= forwardBit;if (r.mPreserveWindow) {
                a.mWindowAdded = true;
                r.mPreserveWindow = false;
                // Normally the ViewRoot sets up callbacks with the Activity
                // in addView->ViewRootImpl#setView. If we are instead reusing
                // the decor view we have to notify the view root that the
                // callbacks may have changed.
                ViewRootImpl impl = decor.getViewRootImpl();
                if (impl != null) {
                    impl.notifyChildRebuilt();
                }
            }
            if (a.mVisibleFromClient) {
                if(! a.mWindowAdded) { a.mWindowAdded =true;
                    wm.addView(decor, l);
                } else {
                    // The activity will get a callback for this {@link LayoutParams} change
                    // earlier. However, at that time the decor will not be set (this is set
                    // inthis method), so no action will be taken. This call ensures the // callback occurs with the decor set. a.onWindowAttributesChanged(l);  }}... }Copy the code

In the handleResumeActivity method, get the Window object associated with the activity, the DecorView object, and the windowManager object, which is an abstract class. WindowManagerImpl: WindowManagerImpl: WindowManagerImpl: WindowManagerImpl: WindowManagerImpl

public final class WindowManagerImpl implements WindowManager { private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance(); . @Override public void addView(View view, ViewGroup.LayoutParams params) { mGlobal.addView(view, params, mDisplay, mParentWindow); }}Copy the code

MGlobal is an instance of Windows ManagerGlobal, so let’s look at the Windows ManagerGlobal addView method:

public void addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow) { ... ViewRootImpl root; View panelParentView = null; synchronized (mLock) { ... root = new ViewRootImpl(view.getContext(), display); // 1 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 {
            root.setView(view, wparams, panelParentView); // 2
        } catch (RuntimeException e) {
            // BadTokenException or InvalidDisplayException, clean up.
            synchronized (mLock) {
                final int index = findViewLocked(view, false);
                if (index >= 0) {
                    removeViewLocked(index, true); } } throw e; }}Copy the code

In the above method, we instantiate the ViewRootImpl class, then call the ViewRootImpl#setView method and pass in the DecorView as an argument. Inside this method, A cross-process call is made to WMS (WindowManagerService) to add the DecorView to the Window. In this process, viewrotimPL, DecorView, and WMS are associated with each other. Finally, WMS calls the View script #performTraverals method to start the View measurement, layout, drawing process. DecorView and Viewrotimpl

That’s all for the DecorView. Let’s start the process of drawing the view.

2. View drawing process

View drawing process in the next article: Review the principle of View drawing (2)