Android Drawing process (Create from Activity -> Content Display)

If, time is a song, then we are the singer, even lonely, will still stubbornly sing

background

At the beginning of Android development, Shui Shui’s impression of starting a “page” and going to the page “display” execution process:

  • StartActivity Starts a new Activity
  • Activity executes the onCreate life cycle
  • SetContentView loads the Layout
  • The onResume application is displayed in the foreground

The content that needs to be displayed is usually written into an XML layout file, which is then displayed in the foreground after the Activity is created.

After learning about ActivityThread, PhoneWindow, ViewRootImpl, and DecorView, which are the key components of UI drawing, Shui Shui will be the Android drawing process from Activity creation to page display in the key classes, key methods for integration.

instructions

Source code is based on Android API 29/ AndroidX 1.1.0

Key class: ActivityThread

An ActivityThread is an Android main thread or UI thread. The main method of an ActivityThread can be seen as the entry point to an App. For information about ActivityThread and App startup, see this article. In this article, we focus on two important methods in ActivityThread for the UI drawing process at startup. The process executed in the ActivityThread after the LaunchActivityItem lifecycle event, in which two key methods are executed:

  • ActivityThread&performLaunchActivity
  • ActivityThread&handleResumeActivity

This article starts with an analysis of which classes are created and which methods are executed from the Activity creation -> Content display.

Key method performLaunchActivity:

As the name suggests, this is a method to Launch the Activity, entering the code:

ActivityThread.java -> performLaunchActivity
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {...// Create the ContextImpl for the Activity
        ContextImpl appContext = createBaseContextForActivity(r);
        Activity activity = null;
        try {
            java.lang.ClassLoader cl = appContext.getClassLoader();
            // Create an Activity with Instrumentation
            // Final newInstance in Instrumentation creates an Activity with reflectionactivity = mInstrumentation.newActivity( cl, component.getClassName(), r.intent); StrictMode.incrementExpectedActivityCount(activity.getClass()); . }try{...// Key method execution, attach method of 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, r.assistToken); . activity.mCalled =false;
                // Execute the callActivityOnCreate in Instrumentation, and finally execute the onCreate life cycle of the Activity
                if (r.isPersistable()) {
                    mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
                } else{ mInstrumentation.callActivityOnCreate(activity, r.state); }}catch (SuperNotCalledException e) {
        ......

        return activity;
}

Copy the code

CallActivityOnCreate method in Instrumentation:

Instrumentation.java -> callActivityOnCreate
public void callActivityOnCreate(Activity activity, Bundle icicle, PersistableBundle persistentState) {
        prePerformCreate(activity);
        // Execute the Activity's performCreate
        activity.performCreate(icicle, persistentState);
        postPerformCreate(activity);
}
Copy the code

The prePerformCreate method in the Activity:

@UnsupportedAppUsage final void performCreate(Bundle icicle, PersistableBundle persistentState) { ..... PersistentState! = null) { onCreate(icicle, persistentState); } else { onCreate(icicle); }... }Copy the code

Stage summary

In the performLaunchActivity method, you can see:

  • The creation of the actual execution class ContextImpl that performs the Activity’s Context
  • After creating the Activity, create the Activity in Instrumentation using newInstance, reflection
  • After creating the Activity, the key method is called: Attach before the onCreate method executes
  • After the attach method is executed, the Activity’s onCreate lifecycle method is finally executed through layer upon layer invocations

Execute process: performLaunchActivity -> Activity Creation -> Activtiy.attach -> Activity.onCreate

Drill down into the ATTACH method to see what you’re doing:

Activity.java -> attach
@UnsupportedAppUsage
    final void attach(Context context, ActivityThread aThread, ...... { attachBaseContext(context);

        mFragments.attachHost(null /*parent*/);
        
        // Performs the creation of PhoneWindow and assigns it to mWindow in the Activity
        mWindow = new PhoneWindow(this, window, activityConfigCallback);
        mWindow.setWindowControllerCallback(this);
        mWindow.setCallback(this);
        mWindow.setOnWindowDismissedCallback(this);
        mWindow.getLayoutInflater().setPrivateFactory(this);
        if(info.softInputMode ! = WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) { mWindow.setSoftInputMode(info.softInputMode); }if(info.uiOptions ! =0) {
            mWindow.setUiOptions(info.uiOptions);
        }
        // Assign the current thread to mUiThread, i.e. ActivityThread == UIThreadmUiThread = Thread.currentThread(); mMainThread = aThread; mInstrumentation = instr; mToken = token; mAssistToken = assistToken; ./ / set the WindowManagermWindow.setWindowManager( (WindowManager)context.getSystemService(Context.WINDOW_SERVICE), mToken, mComponent.flattenToString(), (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) ! =0);
        if(mParent ! =null) { mWindow.setContainer(mParent.getWindow()); } mWindowManager = mWindow.getWindowManager(); . }Copy the code

From the code above, you can see that the PhoneWindow is created in the Attach method of the Activity and assigned to the mWindow variable in the Activity. PhoneWindow is the only implementation class for Windows in Android. Window can be regarded as the abstraction of Windows in the Framework layer of Android display system, and PhoneWindow is the concrete implementation class of this abstraction. For Windows and the Android display system, see this article.

Go to the onCreate method:

class MainActivity : AppCompatActivity(a){

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
    }
}
Copy the code

It’s going to execute the setContentView method inside the onCreate method

Dive into the setContentView and take a quick look at what’s going on:

AppCompatDelegateImpl just as its name implies is AppCompatActivity compatibility (Activity) of the realization of the delegate class AppCompatDelegateImpl. Java - > the setContentView@Override
public void setContentView(int resId) {
    // Create SubDecor, and DecorView, and SubDecor to complete the DecorView replacement
    // The SubDecor here, as the name implies, is an alternate nature of the ViewGroup
    ensureSubDecor();
    // Get the ViewGroup corresponding to R.D.C. Tent in mSubDecor
    ViewGroup contentParent = mSubDecor.findViewById(android.R.id.content);
    // Empties the child views in the ViewGroup
    contentParent.removeAllViews();
    // The resource files for resId are converted into View trees by LayoutInflater objects and added to the mContentParent View
    LayoutInflater.from(mContext).inflate(resId, contentParent);
    mAppCompatWindowCallback.getWrapped().onContentChanged();
}
Copy the code

EnsureSubDecor () method:

AppCompatDelegateImpl.java -> ensureSubDecor
private void ensureSubDecor(a) {
    if(! mSubDecorInstalled) {/ / create SubDecormSubDecor = createSubDecor(); . }}Copy the code
AppCompatDelegateImpl.java -> createSubDecor
private ViewGroup createSubDecor(a) {
        // Get the set App theme and the values of the TITLE, ACTION_BAR and other properties in the theme
        TypedArray a = mContext.obtainStyledAttributes(R.styleable.AppCompatTheme);
        if(! a.hasValue(R.styleable.AppCompatTheme_windowActionBar)) { a.recycle();throw new IllegalStateException(
                    "You need to use a Theme.AppCompat theme (or descendant) with this activity.");
        }

        if (a.getBoolean(R.styleable.AppCompatTheme_windowNoTitle, false)) { requestWindowFeature(Window.FEATURE_NO_TITLE); }... mIsFloating = a.getBoolean(R.styleable.AppCompatTheme_android_windowIsFloating,false);
        a.recycle();

        // Now let's make sure that the Window has installed its decor by retrieving it
        ensureWindow();
        // Get a DecorView and finally execute class PhoneWindow, calling the installDecor method in PhoneWindow
        mWindow.getDecorView();

        final LayoutInflater inflater = LayoutInflater.from(mContext);
        ViewGroup subDecor = null;

        // Load the corresponding layout and assign it to subDecor according to the Window property set above
        if(! mWindowNoTitle) {if (mIsFloating) {
                ......
                subDecor = (ViewGroup) inflater.inflate(
                        R.layout.abc_dialog_title_material, null);
                mHasActionBar = mOverlayActionBar = false;
            } else if (mHasActionBar) {
               
                ......
                subDecor = (ViewGroup) LayoutInflater.from(themedContext)
                        .inflate(R.layout.abc_screen_toolbar, null); . }}else {
            if (mOverlayActionMode) {
                subDecor = (ViewGroup) inflater.inflate(
                        R.layout.abc_screen_simple_overlay_action_mode, null);
            } else {
                subDecor = (ViewGroup) inflater.inflate(R.layout.abc_screen_simple, null); }...// Assign the View corresponding to R.D.Twele in subDecor to mTitleView. Title is this View in AppCompatActivity or Dialog under the AppCompat package. The setTitle method under the Activity calls a method on AppCompatDelegateImpl as a delegate.

        if (mDecorContentParent == null) { mTitleView = (TextView) subDecor.findViewById(R.id.title); }...// Make the decor optionally fit system windows, like the window's decorViewUtils.makeOptionalFitsSystemWindows(subDecor); .// contentView here corresponds to contentView in subDecor
        final ContentFrameLayout contentView = (ContentFrameLayout) subDecor.findViewById(
                R.id.action_bar_activity_content);
        // The windowContentView here corresponds to the View corresponding to R.i c tent in the DecorView
        final ViewGroup windowContentView = (ViewGroup) mWindow.findViewById(android.R.id.content);
        if(windowContentView ! =null) {
            // If some child views have been added to the windowContentView, merge them
            while (windowContentView.getChildCount() > 0) {
                // Remove the child View from the DecorView and add the removed child View to the subDecor contentView
                final View child = windowContentView.getChildAt(0);
                windowContentView.removeViewAt(0);
                contentView.addView(child);
            }

            // Set the contentView in the original DecorView to NO_ID
            windowContentView.setId(View.NO_ID);
            // Set the id of contentView in subDecor after merge to R.I.D.C ontent to complete the last step before replacement
            contentView.setId(android.R.id.content);

            // The decorContent may have a foreground drawable set (windowContentOverlay).
            // Remove this as we handle it ourselves
            if (windowContentView instanceof FrameLayout) {
                ((FrameLayout) windowContentView).setForeground(null); }}// Now set the Window's content view with the decor
        // Finally, subDecor is passed into PhoneWindow by setContentView. In essence, subDecor is added to PhoneWindow contentParent by addView to complete the replacement.mWindow.setContentView(subDecor); .return subDecor;
    }
Copy the code

AppCompatActivity and Activity can create a DecorView within setContentView and within the process of setContentView. AppCompatActivity can also create a subDecor replacement ViewGroup based on the properties set to Windows. This ViewGroup can be created and selected based on the theme of the AppCompat package. Such as a common Theme. AppCompat. Light. DarkActionBar.

<! -- Base application theme. --> <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar"> <! -- Customize your theme here. --> <item name="colorPrimary">@color/colorPrimary</item> <item name="colorPrimaryDark">@color/colorPrimaryDark</item> <item name="colorAccent">@color/colorAccent</item> </style>Copy the code

After creating both, find the content in the original DecorView by findViewById(Android.r.i. D.c ontent) in PhoneWindow, remove the sub-view from the original content and add it to subDecor. Reset the ID of the content in subDecor and DecorView. The ID of subDecor will be R.D.C. Tent, and add it to the contentParent in PhoneWindow to complete the replacement process.

Mwindow.getdecorview () creates a DecorView in PhoneWindow.

PerformLaunchActivity summary:

In the performLaunchActivity process, it is executed successively

1. The Activity to create

2. The Activity of the attch

  • PhoneWindow and the corresponding WindowManager are created

3. The Activity’s onCreate

  • SetContentView completes the creation of the DerView

In the performLaunchActivity process, load the layout file corresponding to the resId setting in the Activity from the DecorView to the last child View. (This process involves XML parsing, recursion, and reflection. See this article for details. However, the view is not displayed at the onCreate position of the Activity, so you need to execute the performResumeActivity method again.

Key method handleResumeActivity:

ActivityThread.java -> handleResumeActivity
@Override
public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
            String reason) {...// performResumeActivity eventually executes the onResume lifecycle method in the Activity
        finalActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason); .if (a.mVisibleFromClient) {
                if(! a.mWindowAdded) { a.mWindowAdded =true;
                    / / WindowManager. AddView methodwm.addView(decor, l); }... }... Looper.myQueue().addIdleHandler(new Idler());
}

Copy the code

You can see that the onResume lifecycle is executed before the DecorView is added to the Window, of course if mWindowAdded is false during the first time the Activity is started.

WindowManager has a corresponding implementation class, WindowManagerImpl. Of course WindowManagerImpl is not the final method execution class, WindowManagerGlobal is, and it is a singleton. Look again at the addView method in Windows ManagerGlobal:

WindowManagerGlobal.java -> addView
public void addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow) {... Life ViewRootImpl ViewRootImpl root; View panelParentView =null;

        synchronized (mLock) {
            ......

            / / create ViewRootImpl
            root = new ViewRootImpl(view.getContext(), display);

            view.setLayoutParams(wparams);

            mViews.add(view);
            mRoots.add(root);
            mParams.add(wparams);
            try {
                // Call the ViewRootImpl setView method
                root.setView(view, wparams, panelParentView);
            } catch (RuntimeException e) {
                // BadTokenException or InvalidDisplayException, clean up.
                if (index >= 0) {
                    removeViewLocked(index, true);
                }
                throwe; }}}Copy the code
ViewRootImpl.java -> setView
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
        synchronized (this) {
            if (mView == null) {
                // Assign the DecorView to the mView variablemView = view; . mAttachInfo.mDisplayState = mDisplay.getState();/ / execution requestLayoutrequestLayout(); . }... }Copy the code

Above, you can see that the performResumeActivity, in the addView method, mainly creates the ViewRootImpl corresponding to the Window. After creating the ViewRootImpl, pass the DecorView into it. Finally, the RequestLayout method of ViewRootImpl is executed.

What is ViewRootImpl?

ViewRootImpl is the parent class of a DecorView. When you do a drawing or refresh API in your code, you usually go from the child View level up through several viewGroups to the DecorView. DecorView will call its parent method, the corresponding draw or refresh API in ViewRootImpl, which will often end up going to scheduleTraversals in ViewRootImpl, sending the refresh event to Choreographer, Choreography will request the VSync refresh signal, execute the doFrame method when the signal arrives, and then go back to the ViewRootImple to execute performTraversals. See this article about Choreography and its refresh mechanism.

ViewRootImpl.java -> performTraversals
private void performTraversals(a) {
        // The mView here is a DecorView passed in through the setView method
        finalView host = mView; . performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); . performLayout(lp, mWidth, mHeight); . performDraw(); }Copy the code

This corresponds to the three important methods in DecorView: measure, layout, and draw.

ViewRootImpl.java -> performMeasure
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
        if (mView == null) {
            return;
        }
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
        try {
            / / execution DecorView. Measure
            mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
        } finally{ Trace.traceEnd(Trace.TRACE_TAG_VIEW); }}Copy the code
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
            int desiredWindowHeight) {
        // Determine if the DecorView is empty
        final View host = mView;
        if (host == null) {
            return; }...// Execute the layout method of the DecorView
       host.layout(0.0, host.getMeasuredWidth(), host.getMeasuredHeight());
       if (numViewsRequestingLayout > 0) {
           ArrayList<View> validLayoutRequesters = getValidLayoutRequesters(mLayoutRequesters,
                        false);
          if(validLayoutRequesters ! =null) {
              // Execute the layout method of the DecorView
              host.layout(0.0, host.getMeasuredWidth(), host.getMeasuredHeight()); }... }Copy the code

PerformDraw () is a lot of stuff, so I won’t expand it here.

summary

Before the handleResumeActivity, the DecorView is created but not drawn. In the handleResumeActivity, the onResume life cycle is executed and ViewRootImpl is created as a parent class of the DecorView. Choreography is configured to refresh and draw the ViewTree.

Thus it can be seen that:

During the initial creation of an Activity, during the onResume lifecycle, the control tree is already created, that is, the UI controls can be retrieved by findViewById. However, during this period, the content is not displayed. Instead, it is requested to refresh the system, and when the signal arrives, it is drawn (measure, Layout, draw) work.

conclusion

It is executed in sequence from performLaunchActivity to handleResumeActivity

1. The Activity to create

2. The Activity of the attch

  • PhoneWindow and the corresponding WindowManager are created

3. The Activity’s onCreate

  • SetContentView completes the creation of the DerView

4. The Activity’s onResume

5.WindowMananger.addView

  • Create the ViewRootImpl and pass in the DecorView
  • Request a refresh signal through Choreography

6. The VSync refresh signal is displayed

  • ViewRootImpl execution performTraversals
  • ViewTree control tree, implement measure, layout, draw methods

The DRAW method has various implementation mechanisms depending on whether hardware acceleration is turned on or not.

I am Zheng Dripping of super water, if there is any wrong place, welcome to point out! Above, thank you!

Refer to the article

Understanding ActivityThread and APP startup process

Android UI display principle of Surface creation

Android application setContentView and LayoutInflater load parsing mechanism source analysis

“Finally Understood” Series: Android Screen Refresh Mechanisms – VSync, Choreographer Understand! .