In Android, an Activity exists as the carrier of an application. It represents a complete user interface and provides a window to draw various views. When an Activity starts, we set up a content view using the setContentView method. This content view is the interface the user sees. So how are views and activities related?

Android UI level drawing system

The diagram above shows the relationship between a View and an Activity

  • PhoneWindow: Each Activity creates a Window to host the display of the View. Window is an abstract class. PhoneWindow is the only implementation class of Window that contains a DecorView.
  • DecorView: The top-level View inherits from FrameLayout and contains two internal parts: ActionBar and ContentView
  • ContentView: The layout we pass in setContentView() loads the display into that View
  • ViewRootImpl: This class has an instance of a DecorView, which controls the DecorView and finally opens the entire View tree by executing performTraversals() of the ViewRootImpl.

View loading process

  • The PhoneWindow class setContentView method is called when the Activity setContentView method is called
public void setContentView(@LayoutRes int layoutResID) {
    getWindow().setContentView(layoutResID);
    initWindowDecorActionBar();
}
Copy the code
  • A DecorView object is eventually generated in the setContentView method of the PhoneWindow class
Public void setContentView(int layoutResID) {if (mContentParent == null) {// Generate a DecorView installDecor();  } else if (! hasFeature(FEATURE_CONTENT_TRANSITIONS)) { mContentParent.removeAllViews(); }... } private void installDecor() { mForceDecorInstall = false; //mDecor for DecorView 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); }... } protected DecorView generateDecor(int featureId) { ... Return new DecorView(context, featureId, this, getAttributes()); }Copy the code
  • The DecorView container contains the root layout, which contains a FrameLayout layout named Content. The Activity loads the XML of the layout and then parses the contents of the XML file into a View hierarchy using LayoutInflater. Finally, add it to the FrameLayout layout with id content.
Protected ViewGroup generateLayout(DecorView decor) {// Do some decorating... // Decorate the form with 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); }... mDecor.startChanging(); // Add loaded base layout to mDecor. OnResourcesLoaded (mLayoutInflater, layoutResource); ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT); if (contentParent == null) { throw new RuntimeException("Window couldn't find content container view"); }}Copy the code

At this point, the Actvity drawing is complete

View View drawing process analysis

  • The DecorView is loaded into the Window

Load the DecorView into the Window through the WindowManager in the ActivityThread’s handleResumeActivity() method, which is called by the following code in the ActivityThread

final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) { ... R = performResumeActivity(Token, clearHide, Reason); if (r ! = null) { final Activity a = r.activity; if (localLOGV) Slog.v( TAG, "Resume " + r + " started activity: " + a.mStartedActivity + ", hideForNow: " + r.hideForNow + ", finished: " + a.mFinished); final int forwardBit = isForward ? WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION : 0; boolean willBeVisible = ! a.mStartedActivity; if (! willBeVisible) { try { willBeVisible = ActivityManagerNative.getDefault().willActivityBeVisible( a.getActivityToken()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } if (r.window == null && ! A.finished &&willbevisible) {// getWindow r.window = r.acty.getwindow (); // getDecorView View decor = r.window.getdecorview (); decor.setVisibility(View.INVISIBLE); WindowManager 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; ViewRootImpl impl = decor. GetViewRootImpl (); if (impl ! = null) { impl.notifyChildRebuilt(); } } if (a.mVisibleFromClient && ! a.mWindowAdded) { a.mWindowAdded = true; // Here WindowManager adds DecorView to PhoneWindow wm.addView(decor, L); }}Copy the code

Conclusion: In the handleResumeActivity method of the ActivityThread, The WindowManager adds the DecorView to the PhoneWindow, and the addView() method executes the action of adding the view to the ViewRootImpl. Finally, the View tree is drawn in the performTraversals of the View wrootimpl

The ViewRootImpl’s performTraversals() method completes the detailed view rendering process

private void performTraversals() { if (! mStopped || mReportNextDraw) { ... int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width); int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height); . // Ask host how big it wants to be The parent layout and the child layout together confirm the measurement mode of the child View, confirm the width and height of the sub-layout at the end of the measurement of the child layout, // Only after the execution of this method can obtain the width and height of the View, PerformMeasure (childWidthMeasureSpec, childHeightMeasureSpec); } if (didLayout) {// Start layout. This method is a method in ViewGroup, such as LinerLayout... performLayout(lp, mWidth, mHeight); } if (! cancelDraw && ! NewSurface) {// Start drawing, execute View onDraw () method performDraw(); }}Copy the code

PerformMeasure (),performLayout(),performDraw(

  • performMeasure()
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) { Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure"); try { mView.measure(childWidthMeasureSpec, childHeightMeasureSpec); } finally { Trace.traceEnd(Trace.TRACE_TAG_VIEW); }}Copy the code

ChildWidthMeasureSpec; childHeightMeasureSpec; childWidthMeasureSpec; childHeightMeasureSpec; childWidthMeasureSpec; So in the onMeasure() method we can use this parameter to get the measurement mode and width information, and we can set the width and height information in the onMeasue as MeasureSpec.

*/ public static class MeasureSpec {private static final int MODE_SHIFT = 30; // Static final int MODE_SHIFT = 30; private static final int MODE_MASK = 0x3 << MODE_SHIFT; /** @hide */ @IntDef({UNSPECIFIED, EXACTLY, AT_MOST}) @retention (retentionpolicy.source) public @interface MeasureSpecMode {} UNSPECIFIED, EXACTLY, AT_MOST //UNSPECIFIED: UNSPECIFIED mode. The parent container does not limit the size of the View, which is used for internal measurement. Public static Final int UNSPECIFIED = 0 << MODE_SHIFT; / / AT_MOST: Max mode, which corresponds to the wrAP_content property specified in the XML file. The final size of the child View is the size value specified by the parent View. Public static final int EXACTLY = 1 << MODE_SHIFT; / / EXACTLY: Public static final int AT_MOST = 2 << MODE_SHIFT; @measurespecMode public static int getMode(int measureSpec) {// NoInspection ResourceType return (measureSpec &)  MODE_MASK); } public static int measureSpec (measureSpec) {return (measureSpec & ~MODE_MASK); }... }Copy the code

PerformMeasure () continues to call the mView.measure() method

public final void measure(int widthMeasureSpec, int heightMeasureSpec) { boolean optical = isLayoutModeOptical(this); if (optical ! = isLayoutModeOptical(mParent)) { Insets insets = getOpticalInsets(); int oWidth = insets.left + insets.right; int oHeight = insets.top + insets.bottom; WidthMeasureSpec = MeasureSpec. Adjust (widthMeasureSpec, optical? -oWidth : oWidth); heightMeasureSpec = MeasureSpec.adjust(heightMeasureSpec, optical ? -oHeight : oHeight); }... if (forceLayout || needsLayout) { // first clears the measured dimension flag mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET; resolveRtlPropertiesIfNeeded(); int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key); if (cacheIndex < 0 || sIgnoreMeasureCache) { // measure ourselves, OnMeasure (widthMeasureSpec, heightMeasureSpec); onMeasure(widthMeasureSpec, heightMeasureSpec); . }... }Copy the code

The onMeasure () method is executed. If the control is a View, the measurement ends here. If the control is a ViewGroup, the loop will continue to get all the sub-views and call the measure method of the sub-views

@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { if (mOrientation == VERTICAL) { measureVertical(widthMeasureSpec, heightMeasureSpec); } else { measureHorizontal(widthMeasureSpec, heightMeasureSpec); }}Copy the code

LinearLayout performs different measurement methods with different placement layouts. Take measureVertical as an example, look down

void measureVertical(int widthMeasureSpec, Final int count = getVirtualChildCount(); . View for (int I = 0; i < count; Final View child = getVirtualChildAt(I); // Call the child View's measure method child.measure(childWidthMeasureSpec, childHeightMeasureSpec); }... }Copy the code

At this point, the measurement process of View ends

View layout process analysis

private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth, int desiredWindowHeight) { final View host = mView; Host. layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight())); }Copy the code
/* *@param l view left edge relative to the parent layout left edge *@param t view top edge relative to the parent layout top edge *@param r view right edge relative to the parent layout left edge distance *@param b view */ public void layout(int l, int t, int r, int b) {... Int oldL = mLeft; int oldT = mTop; int oldB = mBottom; int oldR = mRight; // Call the setFrame method to set the new mLeft, mTop, mBottom, mRight values, Boolean changed = isLayoutModeOptical(mParent)? setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b); // Step 2, If the view position change then call onLayout method set up the view location if (changed | | (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) = = PFLAG_LAYOUT_REQUIRED) {// Start calling onLayout. Place onLayout(changed, L, T, r, b) according to the width and height of the child View and related rules. . }}}}Copy the code

View Draw process analysis

private void performDraw() { ... // call draw method draw(fullRedrawNeeded); . } private void draw(boolean fullRedrawNeeded) { ... // The View's drawSoftware () method is called if (! drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) { return; } private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff, boolean scalingRequired, Rect dirty) { final Canvas canvas; . // Initialize canvas = msurface.lockCanvas (dirty); . // Start calling the ViewGroup and View's draw method mview.draw (canvas); . } public void draw(Canvas canvas) { drawBackground(canvas); //ViewGroup does not call OnDraw if (! dirtyOpaque) onDraw(canvas); // This method is mainly ViewGroup loop call drawChild() to draw the child View dispatchDraw(canvas); } protected void onDraw(Canvas canvas) { }Copy the code

View’s onDraw method is just a template, the specific implementation of the way, we developers to implement

At this point, the View drawing process is complete

  • RequestLayout redraws the view

The child View calls the requestLayout method, which marks the current View and parent container, and submits layer by layer until the ViewRootImpl handles the event. The ViewRootImpl calls three processes, starting with measure, For each view with a marker bit and its child view will be measured, laid out, drawn.

  • Invalidate redraws the view in the UI thread

When a child View calls the invalidate method, a marker bit is added to the View and the parent container is constantly asked to refresh. The parent container calculates the area it needs to redraw until it is passed to the ViewRootImpl and finally the performTraversals method is triggered. Start the View tree redraw process (only the views that need to be redrawn are drawn).

  • PostInvalidate redraws the view in a non-UI thread

PostInvalidate is called from a non-UI thread, while invalidate is called from a UI thread.