In an Activity, we load the XML layout with setContentView(Layout). How does this process work?

public class MainActivity extends AppCompatActivity {

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

AppCompatActivity the setContentView () to concrete implementation by AppCompatDelegateImpl AppCompatDelegateImpl. Java

Window mWindow;

@Override
public void setContentView(int resId) {
    ensureSubDecor();
    ViewGroup contentParent = mSubDecor.findViewById(android.R.id.content);
    contentParent.removeAllViews();
    LayoutInflater.from(mContext).inflate(resId, contentParent);
    mAppCompatWindowCallback.getWrapped().onContentChanged();
}

private void ensureSubDecor(a) {
    if(! mSubDecorInstalled) { mSubDecor = createSubDecor(); . }}private ViewGroup createSubDecor(a) {...// Now let's make sure that the Window has installed its decor by retrieving itensureWindow(); mWindow.getDecorView(); . }private void ensureWindow(a) {
    // We lazily fetch the Window for Activities, to allow DayNight to apply in
    // attachBaseContext
    // Assign the Window value of the Activity to mWindow
    if (mWindow == null && mHost instanceof Activity) {
        attachToWindow(((Activity) mHost).getWindow());
    }
    if (mWindow == null) {
        throw new IllegalStateException("We have not been given a Window"); }}Copy the code

EnsureWindow () assigns the Window of the Activity to mWindow, and then calls the getDecoreView() method of the Window.

Loading the Activity View

PhoneWindow

So what is a Window in an Activity?

The only existing implementation of this abstract class is android.view.PhoneWindow, which you should instantiate when needing a Window.

The only implementation class for Window is PhoneWindow, and the Window in the Activity is created when attach() is attached. Let’s look at the getDecoreView() method inside: phoneWindow.java


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

// This is the view in which the window contents are placed. It is either
// mDecor itself, or a child of mDecor where the contents go.
ViewGroup mContentParent;

@Override
public final View getDecorView(a) {
    if (mDecor == null || mForceDecorInstall) {
        installDecor();
    }
    return mDecor;
}
    
@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

MDecore is the top view of the window, and mContentParent is our custom XML layout placement container.

The installDecor() method is responsible for creating mDecor and mContentParent, and then loading our XML layout into mContentParent via LayoutInflater.

Create a DecorView

private void installDecor(a) {
    mForceDecorInstall = false;
    if (mDecor == null) {
        mDecor = generateDecor(-1); . }else {
        mDecor.setWindow(this);
    }
    if (mContentParent == null) { mContentParent = generateLayout(mDecor); . }}Copy the code
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().getResources());
            if(mTheme ! = -1) { context.setTheme(mTheme); }}}else {
        context = getContext();
    }
    return new DecorView(context, featureId, this, getAttributes());
}
Copy the code

We just new a DecorView, and the DecorView inherits FrameLayout.

Create ContentParent

GenerateLayout (mDecor) is called in the installDecor() method above to create Contentparent

protected ViewGroup generateLayout(DecorView decor) {
    // Apply data from current theme.
    // Get the current topicTypedArray a = getWindowStyle(); .// Call setFlags and requestFeature() methods to set the form according to the topic
    mIsFloating = a.getBoolean(R.styleable.Window_windowIsFloating, false);
    int flagsToUpdate = (FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR)
            & (~getForcedWindowFlags());
    if (mIsFloating) {
        setLayout(WRAP_CONTENT, WRAP_CONTENT);
        setFlags(0, flagsToUpdate);
    } else {
        setFlags(FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR, flagsToUpdate);
    }

    if (a.getBoolean(R.styleable.Window_windowNoTitle, false)) {
        requestFeature(FEATURE_NO_TITLE);
    } else if (a.getBoolean(R.styleable.Window_windowActionBar, false)) {
        // Don't allow an action bar if there is no title.requestFeature(FEATURE_ACTION_BAR); }...// Inflate the window decor.
    // Load the corresponding system layout file according to the features set above
    int layoutResource;
    int features = getLocalFeatures();
    // System.out.println("Features: 0x" + Integer.toHexString(features));
    if ((features & (1<< FEATURE_SWIPE_TO_DISMISS)) ! =0) {
        layoutResource = R.layout.screen_swipe_dismiss;
        setCloseOnSwipeEnabled(true);
    } else if(...) {
         ...
    } else {
        // Embedded, so no decoration is needed.
        layoutResource = R.layout.screen_simple;
        // System.out.println("Simple!" );
    }

    mDecor.startChanging();
    // Load the system layout file above into the DecorView
    mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);

    // Find the control with the id content in the system layout fileViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT); .return contentParent;
}
Copy the code

DecorView.java

void onResourcesLoaded(LayoutInflater inflater, int layoutResource) { ... final View root = inflater.inflate(layoutResource, null); . // Put it below the color views. addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT)); . }Copy the code

Set the feature according to the set theme and decide which system layout file to load. In the system layout file, there is a control whose ID is Content, which is ContentParent.

screen_simple.xml

<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

ContentParent is FrameLayout above

Load the incoming layout file

Let’s go back to AppCompatDelegateImpl and look at the implementation of setContentView()

@Override
public void setContentView(int resId) {
    ensureSubDecor();
    ViewGroup contentParent = mSubDecor.findViewById(android.R.id.content);
    contentParent.removeAllViews();
    LayoutInflater.from(mContext).inflate(resId, contentParent);
    mAppCompatWindowCallback.getWrapped().onContentChanged();
}
Copy the code

From the above analysis, we know that ensureSubDecor() creates a DecorView and ContentParent for PhoneWindow. The incoming layout is then loaded into the ContentParent layout via LayoutInflater.

summary

Add the view to the Window

Only the view was created above, but the view has not been added to the window.

We start the analysis with the Activity launch

Activity Startup process

The Activity is started using the handleLaunchActivity() method of the ActivityThread

ActivityThread.java

private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent, String reason) {... Activity a = performLaunchActivity(r, customIntent);if(a ! =null) {
        r.createdConfig = new Configuration(mConfiguration);
        reportSizeConfigurations(r);
        Bundle oldState = r.state;
        handleResumeActivity(r.token, false, r.isForward,
                !r.activity.mFinished && !r.startsNotResumed, r.lastProcessedSeq, reason);

        ...
    } else{... }}Copy the code
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {... Activity activity =null;
    try {
        java.lang.ClassLoader cl = appContext.getClassLoader();
        // Reflection creates Activity
        activity = mInstrumentation.newActivity(
                cl, component.getClassName(), r.intent);
        StrictMode.incrementExpectedActivityCount(activity.getClass());
        r.intent.setExtrasClassLoader(cl);
        r.intent.prepareToEnterProcess();
        if(r.state ! =null) { r.state.setClassLoader(cl); }}catch (Exception e) {
        if(! mInstrumentation.onException(activity, e)) {throw new RuntimeException(
                "Unable to instantiate activity " + component
                + ":"+ e.toString(), e); }}try{...// Call the attach() method of the Activity
            activity.attach(appContext, this, getInstrumentation(), r.token, r.ident, app, r.intent, r.activityInfo, title, r.parent, r.embeddedID, r.lastNonConfigurationInstances, config, r.referrer, r.voiceInteractor, window, r.configCallback); .if (r.isPersistable()) {
                // Call the Activity's onCreate() method
                mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
            } else{ mInstrumentation.callActivityOnCreate(activity, r.state); }...if(! r.activity.mFinished) {// Call the Activity's onStart() method
                activity.performStart();
                r.stopped = false;
            }
            if(! r.activity.mFinished) {if (r.isPersistable()) {
                    if(r.state ! =null|| r.persistentState ! =null) {
                        // Call the Activity's onRestoreInstanceState() methodmInstrumentation.callActivityOnRestoreInstanceState(activity, r.state, r.persistentState); }}else if(r.state ! =null) { mInstrumentation.callActivityOnRestoreInstanceState(activity, r.state); }}... }... }catch (SuperNotCalledException e) {
        throw e;

    } catch (Exception e) {
        if(! mInstrumentation.onException(activity, e)) {throw new RuntimeException(
                "Unable to start activity " + component
                + ":"+ e.toString(), e); }}return activity;
}
Copy the code

PerformLaunchActivity first creates the Activity with reflection and then calls the Activity’s lifecycle methods: Attach (), onCreate(), onStart(), onRestoreInstance()

The Window is created in the attach() method of the Activity:

mWindow = new PhoneWindow(this, window, activityConfigCallback);

Call setContentView() in the Activity’s onCreate() method to create the DecorView view before adding the DecorView to the Window.

final void handleResumeActivity(IBinder token,
        boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) { ActivityClientRecord r = mActivities.get(token); .// TODO Push resumeArgs into the activity for consideration
    // Execute the Activity's onResume() method
    r = performResumeActivity(token, clearHide, reason);

    if(r ! =null) {
        finalActivity a = r.activity; .if (r.window == null && !a.mFinished && willBeVisible) {
            // Get the Window in the Activity
            r.window = r.activity.getWindow();
            // Get the Window DecorView
            View decor = r.window.getDecorView();
            decor.setVisibility(View.INVISIBLE);
            // Get the WindowManager for the Activity
            ViewManager wm = a.getWindowManager();
            // Get Window LayoutParamsWindowManager.LayoutParams l = r.window.getAttributes(); .if (a.mVisibleFromClient) {
                if(! a.mWindowAdded) { a.mWindowAdded =true;
                    // Add the DecorView to the Window via WindowManager
                    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
                    // in this method), so no action will be taken. This call ensures the
                    // callback occurs with the decor set.a.onWindowAttributesChanged(l); }}... }else{... }}Copy the code

HandleResumeActivity () calls the Activity’s onResume() method and adds the DecorView to the Window.

Next, look at the WindowManager.addView() method. The WindowManger implementation class is WindowManagerImpl WindowManagerImp.java

private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();

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

WindowManagerGlobal.java

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

    synchronized (mLock) {
        ...
        
        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 {
            root.setView(view, wparams, panelParentView);
        } catch (RuntimeException e) {
            // BadTokenException or InvalidDisplayException, clean up.
            if (index >= 0) {
                removeViewLocked(index, true);
            }
            throwe; }}}Copy the code

Create a ViewRootImpl calling its setView() method

ViewRootImpl.java

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
    synchronized (this) {
        if (mView == null) { mView = view; .// Schedule the first layout -before- adding to the window
            // manager, to make sure we do the relayout before receiving
            // any other events from the system.requestLayout(); .// Set DecoreView's Parent to ViewRootImpl
            view.assignParent(this); . }}}@Override
public void requestLayout(a) {
    if(! mHandlingLayoutInLayoutRequest) { checkThread(); mLayoutRequested =true; scheduleTraversals(); }}void scheduleTraversals(a) {
    if(! mTraversalScheduled) { mTraversalScheduled =true;
        mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
        mChoreographer.postCallback(
                Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
        if (!mUnbufferedInputDispatch) {
            scheduleConsumeBatchedInput();
        }
        notifyRendererOfFramePending();
        pokeDrawLockIfNeeded();
    }
}

final TraversalRunnable mTraversalRunnable = new TraversalRunnable();

final class TraversalRunnable implements Runnable {
    @Override
    public void run(a) { doTraversal(); }}void doTraversal(a) {
    if(mTraversalScheduled) { ... performTraversals(); . }}Copy the code

The setView() method basically calls requestLayout() and sets the Parent of the DecorView to ViewRootImpl. RequestLayout () will eventually call the performTraversals() method

private void performTraversals(a) {... performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); . performLayout(lp, mWidth, mHeight); . performDraw(); }Copy the code

This is where the process of drawing the view begins

PerformMeasure () calls mView’s measure() method, and measure() calls onMeasure().

PerformLayout () calls mView’s Layout() method, and Layout() calls onLayout().

PerformDraw () calls mView’s draw() method, and draw() calls onDraw()

summary

The Activity is started with the ActivityThread by calling attach(), which creates the Window, and onCreate(), which creates the layout view with setContentView(), Perform the drawing process of the layout view through ViewRootImpl during the Activity’s resume.