In my last article in the series: Android App/Activity startup process analysis has analyzed an App from clicking its icon to the Activity’s onCreate(), onStart() and onResume(). The entire process whose lifecycle is invoked. As we all know, the contents displayed on the screen of an ordinary App are loaded by the system through self-designed interfaces, and how are the elements in these interfaces rendered? This article will continue to analyze the entire process from a source code perspective based on Android Nougat.

Before we begin, review the sequence diagram from ActivityThread to Activity that was analyzed in the previous article:

Step 1: Initialize PhoneWindow and WindowManager

As shown in the figure above, the Attach () method is called before the Activity’s onCreate(), onStart(), and onResume() lifecycles are called, so we’ll start this article with attach() :

final void attach(Context context, ActivityThread aThread,
        Instrumentation instr, IBinder token, int ident,
        Application application, Intent intent, ActivityInfo info,
        CharSequence title, Activity parent, String id,
        NonConfigurationInstances lastNonConfigurationInstances,
        Configuration config, String referrer, IVoiceInteractor voiceInteractor,
        Window window) { attachBaseContext(context); .// mWindow is an instance of PhoneWindow object
    mWindow = new PhoneWindow(this, window);
    mWindow.setWindowControllerCallback(this);
    mWindow.setCallback(this);
    mWindow.setOnWindowDismissedCallback(this);
    mWindow.getLayoutInflater().setPrivateFactory(this); .// Call the Window setWindowManager methodmWindow.setWindowManager( (WindowManager)context.getSystemService(Context.WINDOW_SERVICE), mToken, mComponent.flattenToString(), (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) ! =0);
    if(mParent ! =null) {
        mWindow.setContainer(mParent.getWindow());
    }
    // Get WindowManager from Window
    mWindowManager = mWindow.getWindowManager();
    mCurrentConfig = config;
}
Copy the code

MWindow is a variable of type Window. In the attach() method, an instance of a PhoneWindow object is created and assigned to mWindow. PhoneWindow directly inherits from the Window class. Then we call the Window’s setWindowManager() method:

public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
        boolean hardwareAccelerated) {...// mWindowManager is an instance of the WindowManagerImpl object
    mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
}
Copy the code

Therefore, the mWindow variable in Acitvity is an instance of the PhoneWindow class, and mWindowManager is an instance of the WindowManagerImpl class, and the main job of the Attach () method is to initialize both variables.

Step 2: Initialize the DecorView

Track code execution

Then we go to the onCreate method, and as we all know, if you want to display your layout file or View in your Activity, We must call setContentView() in the Activity’s onCreate() method to pass in our layout ID or View. Look at one of the setContentView() methods:

public void setContentView(@LayoutRes int layoutResID) {
    getWindow().setContentView(layoutResID);
    initWindowDecorActionBar();
}
Copy the code

Continue looking at the PhoneWindow class setContentView() method:

public void setContentView(int layoutResID) {
    if (mContentParent == null) {// This is the first call
        // Initialize Decor
        installDecor();
    } else if(! hasFeature(FEATURE_CONTENT_TRANSITIONS)) {// Transition animation, default false
        mContentParent.removeAllViews();
    }

    if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
        ...
    } else {
        // Parse the layout filemLayoutInflater.inflate(layoutResID, mContentParent); }... }Copy the code

If this method is called for the first time, mContentParent is null, otherwise remove all mContentParent’s child views if there is no transition animation and continue tracing the installDecor() method:

private void installDecor(a) {
    mForceDecorInstall = false;
    if (mDecor == null) {
        // Generate a DecorView object
        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) {
        // Call the generateLayout methodmContentParent = generateLayout(mDecor); . }}}Copy the code

When mDecor is null, the generateDecor() method is called to create an instance of the DecorView class, which inherits from FrameLayout. If so, call generateLayout(), which creates the mContentParent object and traces it in:

protected ViewGroup generateLayout(DecorView decor) { TypedArray a = getWindowStyle(); .// Initialize Windows with various properties set in WindowStyle
    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); }...int layoutResource;
    int features = getLocalFeatures();

    // Get the corresponding layout file according to the set features value and assign it to layoutResource
    if ((features & (1<< FEATURE_SWIPE_TO_DISMISS)) ! =0) {
        layoutResource = R.layout.screen_swipe_dismiss;
    } else if ((features & ((1 << FEATURE_LEFT_ICON) | (1<< FEATURE_RIGHT_ICON))) ! =0) {
        if (mIsFloating) {
            TypedValue res = new TypedValue();
            getContext().getTheme().resolveAttribute(
                    R.attr.dialogTitleIconsDecorLayout, res, true);
            layoutResource = res.resourceId;
        } else {
            layoutResource = R.layout.screen_title_icons;
        }
        removeFeature(FEATURE_ACTION_BAR);
    } else if ((features & ((1 << FEATURE_PROGRESS) | (1<< FEATURE_INDETERMINATE_PROGRESS))) ! =0
            && (features & (1 << FEATURE_ACTION_BAR)) == 0) {
        layoutResource = R.layout.screen_progress;
    } else if ((features & (1<< FEATURE_CUSTOM_TITLE)) ! =0) {
        if (mIsFloating) {
            TypedValue res = new TypedValue();
            getContext().getTheme().resolveAttribute(
                    R.attr.dialogCustomTitleDecorLayout, res, true);
            layoutResource = res.resourceId;
        } else {
            layoutResource = R.layout.screen_custom_title;
        }
        removeFeature(FEATURE_ACTION_BAR);
    } else if ((features & (1 << FEATURE_NO_TITLE)) == 0) {
        if (mIsFloating) {
            TypedValue res = new TypedValue();
            getContext().getTheme().resolveAttribute(
                    R.attr.dialogTitleDecorLayout, res, true);
            layoutResource = res.resourceId;
        } else if ((features & (1<< FEATURE_ACTION_BAR)) ! =0) {
            layoutResource = a.getResourceId(
                    R.styleable.Window_windowActionBarFullscreenDecorLayout,
                    R.layout.screen_action_bar);
        } else{ layoutResource = R.layout.screen_title; }}else if ((features & (1<< FEATURE_ACTION_MODE_OVERLAY)) ! =0) {
        layoutResource = R.layout.screen_simple_overlay_action_mode;
    } else {
        layoutResource = R.layout.screen_simple;
    }

    mDecor.startChanging();
    // Call onResourcesLoaded
    mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);

    / / in layoutResource according to the id: com. Android. The internal, R.i, dc ontent retain and assigned to a ViewGroup contentParent object
    ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
    if (contentParent == null) {
        throw new RuntimeException("Window couldn't find content container view"); }... mDecor.finishChanging();/ / return contentParent
    return contentParent;
}
Copy the code

DecorView onResourcesLoaded() method:

void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {... mDecorCaptionView = createDecorCaptionView(inflater);// Parse the layoutResource file
    final View root = inflater.inflate(layoutResource, null);
    if(mDecorCaptionView ! =null) {... }else {
        // Added as a root layout to mDecor
        addView(root, 0.new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
    }
    mContentRoot = (ViewGroup) root;
    initializeElevation();
}
Copy the code

As you can see, the generateLayout method is divided into four main parts:

  1. Will first through the com. Android. Internal. R.s tyleable. Window set in the various attributes for each requestFeature or setFlags operation Window.
  2. The next step is to select different window decoration layout files based on the set features values, resulting in layoutResource values. Therefore, methods such as requestFeature() that set the full screen in your Activity need to be called before setContentView so that you can choose a different root layout depending on your Settings.
  3. Pass the layoutResource value to the DecorView’s onResourcesLoaded() method and use LayoutInflater to convert the layout into a View as the root View and add it to mDecor.
  4. To look for in the mDecor id for the com. Android. Internal, R.i, dc ontent of ViewGroup and return as a return value, normally this ViewGroup FrameLayout.

About the fourth, could have some question, why according to id for com. Android. Internal. R.i, dc ontent we will be able to find the corresponding ViewGroup? The answer is in the generateLayout() method we analyzed earlier, which gets the layout file based on the features values and assigns it to layoutResource, All of these layout files have a FrameLayout with an ID of Content, but some layout files may also have ActiionBar, Title, etc. These layout files are stored in this directory. Take r.layout.screen_simple, which reads as follows:

<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

Back to the setContentView() method of PhoneWindow, after installDecor(), mDecor is initialized and mContentParent is assigned a value. Back to setContentView(), The last important step is to use mLayoutInflater. Inflater to push our layout files into the FrameLayout id content in mDecor.

public void setContentView(int layoutResID) {
    if (mContentParent == null) {// This is the first call
        // Initialize Decor
        installDecor();
    } else if(! hasFeature(FEATURE_CONTENT_TRANSITIONS)) {// Transition animation, default false
        mContentParent.removeAllViews();
    }

    if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
        ...
    } else {
        // Parse the layout filemLayoutInflater.inflate(layoutResID, mContentParent); }... }Copy the code

summary

At this point, the setContentView() method is complete, and generally divided into three steps:

  1. Initialize mDecor, which is an instance of the DecorView class that inherits from FrameLayout;
  2. Based on the property values in theme, select the layout file and load it with infalter.inflater() and add it to mDecor. Each of these layout files has a FrameLayout with an ID of Content;
  3. The layout file set in the Activity’s setContentView() method, MLayoutInflater. Inflater () is pushed into the FrameLayout content in mDecor.

The sequence diagram of this process is as follows:

The relationship between Activity, PhoneWindow, DecorView and ContentView is shown below:

However, our layout is not shown at this point, so keep looking.

Step 3: Create the ViewRootImpl and associate the DecorView

Track code execution

As you can see in the opening sequence diagram, the ActivityThread indirectly calls attach() and onCreate() of the Activity in the handleLaunchActivity() method:

private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent) {... WindowManagerGlobal.initialize();// Execute the performLaunchActivity method
    Activity a = performLaunchActivity(r, customIntent);
    if(a ! =null) {
        r.createdConfig = new Configuration(mConfiguration);
        Bundle oldState = r.state;
        // Execute the handleResumeActivity method and finally call the onStart and onResume methods
        handleResumeActivity(r.token, false, r.isForward, ! r.activity.mFinished && ! r.startsNotResumed);if(! r.activity.mFinished && r.startsNotResumed) { r.activity.mCalled =false;
            mInstrumentation.callActivityOnPause(r.activity);
            r.paused = true; }}else {
        // Stop the Activity
        ActivityManagerNative.getDefault()
            .finishActivity(r.token, Activity.RESULT_CANCELED, null.false); }}Copy the code

The handleResumeActivity() method is then called:

final void handleResumeActivity(IBinder token,
            boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) { ActivityClientRecord r = mActivities.get(token); .// Methods such as onStart() and onResume() are eventually called
    r = performResumeActivity(token, clearHide, reason);

    if(r ! =null) {
        finalActivity a = r.activity; .if (r.window == null && !a.mFinished && willBeVisible) {
            r.window = r.activity.getWindow();
            / / get DecorView
            View decor = r.window.getDecorView();
            // Make the DecorView invisible
            decor.setVisibility(View.INVISIBLE);
            // Get ViewManager, here is WindowManagerImpl instanceViewManager wm = a.getWindowManager(); WindowManager.LayoutParams l = r.window.getAttributes(); a.mDecor = decor; l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION; l.softInputMode |= forwardBit; .if(a.mVisibleFromClient && ! a.mWindowAdded) {// Flag set to true
                a.mWindowAdded = true;
                // Call WindowManagerImpl's addView methodwm.addView(decor, l); }}else if (!willBeVisible) {
            ...
        }
        ...
        if(! r.activity.mFinished && willBeVisible && r.activity.mDecor ! =null && !r.hideForNow) {
            ...
            if (r.activity.mVisibleFromClient) {
                // Call the makeVisible method to make the DecorView visibler.activity.makeVisible(); }}... }else {
        try {
            // If an exception occurs during this process, the Activity is killed
            ActivityManagerNative.getDefault()
                .finishActivity(token, Activity.RESULT_CANCELED, null,
                        Activity.DONT_FINISH_TASK_WITH_ACTIVITY);
        } catch (RemoteException ex) {
            throwex.rethrowFromSystemServer(); }}}Copy the code

After executing the performResumeActivity() method, the Activity object in ActivityClientRecord is then retrieved and the DecorView object initialized earlier in the setContentView process is returned. It’s then passed as a parameter to an object of type ViewManager wm’s addView method, ViewManager is an interface, so who implements it? Here’s a quick twist: Look back at the attach() method of the Activity:

final void attach(...). {...// mWindow is a PhoneWindow object
    mWindow = new PhoneWindow(this, window); .// Call the Window setWindowManager methodmWindow.setWindowManager(...) ; . }Copy the code

Tracing the Window’s setWindowManager() method:

public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
        boolean hardwareAccelerated) {...// mWindowManager is an instance of the WindowManagerImpl object
    mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
}
Copy the code

So, back to the main thread, the Activity’s getWindowManager() gets an instance of the WindowManagerImpl object, and look at its addView() method:

public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
    applyDefaultToken(params);
    // mGlobal is an instance of the WindowManagerGlobal object
    mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
Copy the code

MGlobal is an instance of the WindowManagerGlobal object. Look at its addView() method:

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

    synchronized (mLock) {
        ...
        root = newViewRootImpl(view.getContext(), display); . mRoots.add(root); . }try {
        // Add the passed DecorView to the ViewRootImpl
        root.setView(view, wparams, panelParentView);
    } catch(RuntimeException e) { ... }...// Set the parent property of view to ViewRootImpl
    view.assignParent(this); . }Copy the code

We first create a ViewRootImpl object, then pass the DecorView object as a parameter to the ViewRootImpl setView() method, The DecorView adds it and sets its parent property to ViewRootImpl, thus establishing a connection between them.

summary

As you can see, ViewRootImpl is the DecorView manager and is responsible for the measurement, layout, and drawing of the View Tree, as well as controlling the View Tree refresh operation through Choreographer, which we will discuss later.

Step 4: Establish a connection between PhoneWindow and WindowManagerService

As the administrator of all Windows, WMS is responsible for adding and deleting Windows, managing Surface and distributing events, etc. Therefore, if the PhoneWindow object in each Activity needs to be displayed, it must interact with WMS.

Track code execution

To continue the flow of the previous step, look at the setView method of the View wrootimpl:

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
    synchronized (this) {
        if (mView == null) { mView = view; .// 1
            // 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(); .try{...// 2. MWindowSession invokes WMS remotely with Binder
                res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                        getHostVisibility(), mDisplay.getDisplayId(),
                        mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                        mAttachInfo.mOutsets, mInputChannel);
            } catch (RemoteException e) {
                ...
            } finally{... }... view.assignParent(this); . }}}Copy the code

Ignore the requestLayout method for a moment. In note 2, IWindowSession is the proxy object in THE WMS used to receive the Session object called by ViewRootImpl. That is, ViewRootImpl calls WMS methods remotely via IWindowSession:

Session.java

@Override
public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs,
        int viewVisibility, int displayId, Rect outContentInsets, Rect outStableInsets,
        Rect outOutsets, InputChannel outInputChannel) {
    // mService is the WMS object
    return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId,
            outContentInsets, outStableInsets, outOutsets, outInputChannel);
}
Copy the code
WindowManagerService.java

public int addWindow(Session session, IWindow client, int seq,
            WindowManager.LayoutParams attrs, int viewVisibility, int displayId,
            Rect outContentInsets, Rect outStableInsets, Rect outOutsets,
            InputChannel outInputChannel) {...// Create a WindowState object
    WindowState win = new WindowState(this, session, client, token,
                    attachedWindow, appOp[0], seq, attrs, viewVisibility, displayContent); . win.attach();// Create a SurfaceSession
    mWindowMap.put(client.asBinder(), win); // mWindowMap is a set of Windows that WindowManagerService uses to hold all the current Windows. win.mToken.addWindow(win);// There can be multiple WindowStates under one token. In fact, token corresponds to PhoneWindow one by one.. }Copy the code

This is a typical Binder bidirectional communication model. Binder mechanisms can be found with AIDL. This process is shown below:

summary

Summarize the relationship between activities, Windows, and WMS with a diagram:

Step 5: Establish a connection to SurfaceFlinger

SurfaceFlinger is one of Android’s most important system services. It is responsible for Layer composition and rendering (Surface in Native name).

Track code execution

Following the steps above, the WindowState attach() method will call the Session windowAddedLocked() method:

WindowState.java

void attach(a) {
    if (WindowManagerService.localLOGV) Slog.v(
        TAG, "Attaching " + this + " token=" + mToken
        + ", list=" + mToken.windows);
    mSession.windowAddedLocked();
}
Copy the code
Session.java

void windowAddedLocked(String packageName) {...if (mSurfaceSession == null) {... mSurfaceSession =newSurfaceSession(); . }}public final class SurfaceSession {
    private long mNativeClient; // SurfaceComposerClient*
    private static native long nativeCreate(a); .public SurfaceSession(a) { mNativeClient = nativeCreate(); }... }Copy the code

The nativeCreate() method is a native method:

android_view_SurfaceSession.cpp

static jlong nativeCreate(JNIEnv* env, jclass clazz) {
    SurfaceComposerClient* client = new SurfaceComposerClient();
    client->incStrong((void*)nativeCreate);
    return reinterpret_cast<jlong>(client);
}
Copy the code

The nativeCreate method constructs a SurfaceComposerClient object, which acts as a bridge between the application and the SurfaceFlinger. The SurfaceComposerClient pointer calls the following methods when used for the first time:

void SurfaceComposerClient::onFirstRef() {
    ....
    sp<ISurfaceComposerClient> conn;
    // sf is the SurfaceFlinger object pointerconn = (rootProducer ! =nullptr)? sf->createScopedConnection(rootProducer) : sf->createConnection(); . }Copy the code

It creates an ISurfaceComposerClient object via SurfaceFlinger’s createScopedConnection method:

SurfaceFlinger.cpp

sp<ISurfaceComposerClient> SurfaceFlinger::createConnection() {
    return initClient(new Client(this)); 
}

static sp<ISurfaceComposerClient> initClient(const sp<Client>& client) {
    status_t err = client->initCheck();
    // Just check for errors and return to the client itself
    if (err == NO_ERROR) {
        return client;
    }
    return nullptr;
}
Copy the code

The Client class implements the ISurfaceComposerClient(inheriting from IInterface) interface so that it can communicate across processes. SurfaceComposerClient uses it to communicate with SurfaceFlinger. It also creates surfaces and maintains all layers of an application.

The initClicent method does some error checking and returns to the Client itself.

summary

The process is shown below:

Step 6: Apply for a Surface

After steps 4 and 5, the ViewRootImpl is connected to WMS and SurfaceFlinger, but the View is still not displayed. We all know that the UI is ultimately going to be displayed on the Surface, so when was the Surface created? Return to the setView method of ViewRootImpl at the beginning of Step 4:

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(); .try{... res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes, getHostVisibility(), mDisplay.getDisplayId(), mAttachInfo.mContentInsets, mAttachInfo.mStableInsets, mAttachInfo.mOutsets, mInputChannel); }... }}}Copy the code

WMS handles event distribution as well as window management, so before associating with WMS, make sure that the View Tree has performed layout operations to receive events from the WMS.

Trace the requestLayout() method of ViewRootImpl:

public void requestLayout(a) {
    if(! mHandlingLayoutInLayoutRequest) { checkThread(); mLayoutRequested =true; scheduleTraversals(); }}Copy the code

Trace the scheduleTraversals() method of ViewRootImpl:

void scheduleTraversals(a) {
    if(! mTraversalScheduled) { mTraversalScheduled =true;
        // Set synchronization barrier to suspend processing of subsequent synchronization messages
        mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
        // Execute mTraversalRunnable on the next frame
        mChoreographer.postCallback(
                Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); . }}Copy the code

First, set synchronization barriers, suspend processing of subsequent synchronization messages, and then use the Choreographer class to execute mTraversalRunnable objects when the next frame is drawn (about the Choreographer principle, See Choreographer mechanism implementation for Android). Briefly, Choreographer will receive inside VSync signals from the SurfaceFlinger for a VSync cycle of about 16ms.

SurfaceFlinger is initiated by the init process running on the underlying process, a system of its main responsibility is to synthesize and apply colours to a drawing Surface (Layer), vertical sync signal and sent to the target process VSync. Therefore, if you want to receive Vsync signals, you must first establish a connection with SurfaceFlinger, which is why you go to Step 5 first.

MTraversalRunnable is a Runnable object:

final class TraversalRunnable implements Runnable {
    @Override
    public void run(a) { doTraversal(); }}final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
Copy the code

The run() method has only one line of code, and the doTraversal() method looks like this:

void doTraversal(a) {
    if (mTraversalScheduled) {
        mTraversalScheduled = false;
        // Remove synchronization barriersmHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier); .// Enter the View drawing processperformTraversals(); . }}Copy the code

Once the synchronization barrier has been removed, all the preparatory work for rendering has been done, and the performTraversals() method is called to enter the View’s rendering process.

private void performTraversals(a) {
    finalView host = mView; // mView is just a DecorView. relayoutWindow(params, viewVisibility, insetsPending); .// Execute the Measure processperformMeasure(childWidthMeasureSpec, childHeightMeasureSpec); .// Execute the Layout processperformLayout(lp, desiredWindowWidth, desiredWindowHeight); .// Execute Draw processperformLayout(); . }Copy the code

Trace relayoutWindow method:

private int relayoutWindow(WindowManager.LayoutParams params, int viewVisibility,
        boolean insetsPending) throws RemoteException {...int relayoutResult = mWindowSession.relayout(
            mWindow, mSeq, params,
            (int) (mView.getMeasuredWidth() * appScale + 0.5 f),
            (int) (mView.getMeasuredHeight() * appScale + 0.5 f),
            viewVisibility, insetsPending ? WindowManagerGlobal.RELAYOUT_INSETS_PENDING : 0, mWinFrame, mPendingOverscanInsets, mPendingContentInsets, mPendingVisibleInsets, mPendingStableInsets, mPendingOutsets, mPendingBackDropFrame, mPendingConfiguration, mSurface); . }Copy the code

Binder call again (highly recommended), which calls the RELayout method of WMS. Note that the last argument is the SurfaceView object, which was initialized when the ViewRootImpl was defined:

final Surface mSurface = new Surface();

Again, relayoutWindow at WMS:

public int relayoutWindow(Session session, IWindow client, int seq,
            WindowManager.LayoutParams attrs, int requestedWidth,
            int requestedHeight, int viewVisibility, int flags,
            Rect outFrame, Rect outOverscanInsets, Rect outContentInsets,
            Rect outVisibleInsets, Rect outStableInsets, Rect outOutsets, Rect outBackdropFrame,
            Configuration outConfig, Surface outSurface){... result = createSurfaceControl(outSurface, result, win, winAnimator); . }private int createSurfaceControl(Surface outSurface, int result, WindowState win,WindowStateAnimator winAnimator) {... surfaceController = winAnimator.createSurfaceLocked(win.mAttrs.type, win.mOwnerUid); . surfaceController.getSurface(outSurface); }Copy the code

This first calls WindowStateAnimator createSurfaceLocked to generate a truly valid Surface object in the Native layer, The getSurface method then associates the Surface object in the Java layer with it.

Step 7: Formally draw the View

After the Surface has been applied, the View otimpl performTraversals() method will continue to execute. This method is quite verbose, ignoring conditional judgment, and is condensed as follows:

private void performTraversals(a) {... relayoutResult = relayoutWindow(params, viewVisibility, insetsPending); . WindowManager.LayoutParams lp = mWindowAttributes; .// Get the DecorView width and height MeasureSpec
    int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
    intchildHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height); .// Execute the Measure processperformMeasure(childWidthMeasureSpec, childHeightMeasureSpec); .// Execute the Layout processperformLayout(lp, desiredWindowWidth, desiredWindowHeight); .// Execute Draw processperformLayout(); . }Copy the code

First, the values of childWidthMeasureSpec and childHeightMeasureSpec are retrieved from the getRootMeasureSpec() method for drawing the DecorView. Because the DecorView is the root of all the child elements, the layout of the child elements is layered, so all the child elements are measured, laid out, and drawn layer by layer, starting from the DecorView, Corresponding to performMeasure(), performLayout() and performLayout() methods, the whole process is shown as follows:

Understand the MeasureSpec

MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = Measure; MeasureSpec is defined as follows to avoid creating too many objects to reduce memory allocation:

public static class MeasureSpec {
    private static final int MODE_SHIFT = 30;
    private static final int MODE_MASK = 0x3 << MODE_SHIFT;

    // Unrestricted measurement mode: the parent container does not impose any restrictions on the View, the View can be as large as it wants,
    // This pattern is usually used within systems.
    public static final int UNSPECIFIED = 0 << MODE_SHIFT;

    SpecSize: The parent container has determined the size required by the View.
    // When the layout parameter is match_parent or a specific value
    public static final int EXACTLY = 1 << MODE_SHIFT;

    The parent container specifies that the View can only be SpecSize.
    // The layout argument is wrap_content
    public static final int AT_MOST = 2 << MODE_SHIFT;

    // Create a MeasureSpec based on SpecMode and SpecSize
    public static int makeMeasureSpec(int size, int mode) {
        if (sUseBrokenMakeMeasureSpec) {
            return size + mode;
        } else {
            return(size & ~MODE_MASK) | (mode & MODE_MASK); }}/ / get SpecMode
    public static int getMode(int measureSpec) {
        return (measureSpec & MODE_MASK);
    }

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

    / / adjust the MeasureSpec
    static int adjust(int measureSpec, int delta) {
        final int mode = getMode(measureSpec);
        if (mode == UNSPECIFIED) {
            return make MeasureSpec(0, UNSPECIFIED);
        }
        int size = getSize(measureSpec) + delta;
        if (size < 0) {
            size = 0;
        }
        returnmakeMeasureSpec(size, mode); }}Copy the code

Looking at the getRootMeasureSpec() method, the first argument passed is the width or height of the entire screen, and the second argument is the Window’s LayoutParams:

private static int getRootMeasureSpec(int windowSize, int rootDimension) {
    int measureSpec;
    switch (rootDimension) {
    case ViewGroup.LayoutParams.MATCH_PARENT:
        measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
        break;
    case ViewGroup.LayoutParams.WRAP_CONTENT:
        measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
        break;
    default:
        measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
        break;
    }
    return measureSpec;
}
Copy the code

From the code logic, the DecorView’s MeasureSpec is generated as follows:

  • LayoutParams = MATCH_PARENT: Exact mode, size is the width or height of the screen;
  • LayoutParams = WRAP_CONTENT: Cannot exceed the width or height of the screen;
  • Fixed size: Exact mode, size is the value specified in LayoutParams.

But for ordinary views, the View’s measure() method is called by the parent ViewGroup. Look at the measureChildWithMargins() method of the ViewGroup:

protected void measureChildWithMargins(View child,
        int parentWidthMeasureSpec, int widthUsed,
        int parentHeightMeasureSpec, int heightUsed) {
    // Gets the layout parameters of the child elements
    final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
    // MeasureSpec produces the child element
    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);
}
Copy the code

As you can see, before calling the measure() method of the child element, the getChildMeasureSpec() method produces the child element’s MeasureSpec. In addition to the parent’s MeasureSpec and the child’s LayoutParams, the child’s Margin and the parent’s Padding are also related to the size of the child. The getChildMeasureSpec() method does this:

public static int getChildMeasureSpec(int spec, int padding, int childDimesion) {
    int specMode = MeasureSpec.getMode(spec);
    int specSize = MeasureSpec.getSize(spec);

    // The maximum available space of the child element is the size of the parent container minus the size of the space already occupied by the parent
    int size = Math.max(0, specSize - padding);

    int resultSize = 0;
    int resultMode = 0;

    switch (sepcMode) {
        // 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 (childDimesion == 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 = 0;
                resultMode = MeasureSpec.UNSPECIFIED;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                // Child wants to determine its own size....
                // find out how big it should be
                resultSize = 0;
                resultMode == MeasureSpec.UNSPECIFIED;
            }
            break;
        }
    return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
Copy the code

The whole process is a bit more complicated and can be seen in the following table:

Measure process analysis

Back in the performTraversals() method, the DecorView’s MeasureSpec is retrieved and the performMeasure() method is called:

private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {... mView.measure(childWidthMeasureSpec, childHeightMeasureSpec); . }Copy the code

The mView is a DecorView instance passed in via the setView() method that inherits from FrameLayout, which in turn is a ViewGroup that also inherits from the View. The View’s measure() method is final and cannot be overridden by subclasses, so it is actually the View’s measure method that is called:

public final void measure(int widthMeasureSpec, int heightMeasureSpec) {... onMeasure(widthMeasureSpec, heightMeasureSpec); . }Copy the code

View onMeasure()

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
            getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
Copy the code

As you can see, the View’s default onMeasure() method first calls getDefaultSize() to get the default width and height values, and then setMeasuredDimension() to set the values. Look at the code for getDefaultSize() :

public static int getDefaultSize(int size, int measureSpec) {
    int result = size;
    int specMode = MeasureSpec.getMode(measureSpec);
    int specSize = MeasureSpec.getSize(measureSpec);

    switch (specMode) {
        case MeasureSpec.UNSPECIFIED:
            result = size;
            break;
        case MeasureSpec.AT_MOST:
        case MeasureSpec.EXACTLY:
            result = specSize;
            break;
    }
    return result;
}
Copy the code

Either AT_MOST or EXACTLY returns a measureSpec specSize, which is the final result of the measurement. In the case of UNSPECIFIED, it returns a recommended minimum value that is dependent on the background size of the minimum set by the child element.

As you can see from the default implementation of onMeasure(), if we define a control that inherits directly from the View and do not override the onMeasure() method, When you use this control and set layout_width or layout_height to wrap_content, it will look like match_parent! When wrap_content is used in the layout, the specMode is AT_MOST and the specSize is parentSize. GetDefaultSize () returns specSize, so the width or height of the child element is set to the size of the remaining space of the parent container. A common solution is to override the onMeasure() method as follows:

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);

    int widthMode = MeasureSpec.getMode(widthMeasureSpec);
    int widthSize = MeasureSpec.getSize(widthMeasureSpec);

    int heightMode = MeasureSpec.getMode(heightMeasureSpec);
    int heightSize = MeasureSpec.getSize(heightMeasureSpec);

    // Default width/height
    int mWidth = default_width;
    int mHeight = default_height;

    // When the layout parameter is set to wrap_content, use the default value
    if (getLayoutParams().width == ViewGroup.LayoutParams.WRAP_CONTENT && getLayoutParams().height == ViewGroup.LayoutParams.WRAP_CONTENT) {
        setMeasuredDimension(mWidth, mHeight);
    // Width/height The default values are used when any of the layout parameters are wrap_content
    } else if (getLayoutParams().width == ViewGroup.LayoutParams.WRAP_CONTENT) {
        setMeasuredDimension(mWidth, heightSize);
    } else if(getLayoutParams().height == ViewGroup.LayoutParams.WRAP_CONTENT) { setMeasuredDimension(widthSize, mHeight); }}Copy the code

Because DecorView inherits from FrameLayout, which is a ViewGroup, the ViewGroup is an abstract class that does not define a specific measurement process and uses the View’s onMeasure() by default. DecorView rewrites the onMeasure() method to override the onMeasure() method. Each subclass has different layout properties and therefore needs different measurement logic.

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {...final int widthMode = getMode(widthMeasureSpec);
    final intheightMode = getMode(heightMeasureSpec); .if(widthMode == AT_MOST) { ... }...if(heightMode == AT_MOST) { ... }...super.onMeasure(widthMeasureSpec, heightMeasureSpec); . }Copy the code

The onMeasure() method of the parent FrameLayout class will be called as follows:

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    intcount = getChildCount(); .for (int i = 0; i < count; i++) {
        final View child = getChildAt(i);
        if(mMeasureAllChildren || child.getVisibility() ! = GONE) { measureChildWithMargins(child, widthMeasureSpec,0, heightMeasureSpec, 0); . }}... setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState), resolveSizeAndState(maxHeight, heightMeasureSpec, childState << MEASURED_HEIGHT_STATE_SHIFT)); . }Copy the code

As you can see, the measureChildWithMargins() method is iterated over each of its children, and the measureChildWithMargins() method is already there to calculate the child’s MeasureSpec and then call the child’s measure() method:

protected void measureChildWithMargins(View child,
        int parentWidthMeasureSpec, int widthUsed,
        int parentHeightMeasureSpec, int heightUsed) {
    // Gets the layout parameters of the child elements
    final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
    // MeasureSpec produces the child element
    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);
}
Copy the code

At this time, the measure() method of the View is actually called. As can be seen from the above content, the onMeasure() method of the child element will be called again, so that the onMeasure() method of each child element is recursively called to measure.

Layout Process Analysis

Back to the performLayout() method, after performMeasure() traverses all child elements, the performLayout() method is called:

private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth, int desiredWindowHeight) {...// Call the layout() method of the DecorView
    host.layout(0.0, host.getMeasuredWidth(), host.getMeasuredHeight()); . }Copy the code

GetMeasuredWidth () and getMeasuredHeight() are the measurements calculated by the DecorView in the previous Measure process, and are passed as arguments to the Layout () method. The View layout() method is called:

public void layout(int l, int t, int r, int b) {...// Whether the View state has changed
    boolean changed = isLayoutModeOptical(mParent) ?
                setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
    // If the View state changes, the layout is rearranged
    if(changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) { onLayout(changed, l, t, r, b); . }... }Copy the code

SetOpticalFrame () also calls the setFrame() method directly inside setOpticalFrame().

protected boolean setFrame(int left, int top, int right, int bottom) {
    boolean changed = false;

    if(mLeft ! = left || mRight ! = right || mTop ! = top || mBottom ! = bottom) { changed =true; .int oldWidth = mRight - mLeft;
        int oldHeight = mBottom - mTop;
        int newWidth = right - left;
        int newHeight = bottom - top;
        booleansizeChanged = (newWidth ! = oldWidth) || (newHeight ! = oldHeight);// Invalidate our old position
        invalidate(sizeChanged);

        // Initialize the variable

        mLeft = left;
        mTop = top;
        mRight = right;
        mBottom = bottom;
        // Update the display list for renderingmRenderNode.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom); .if (sizeChanged) {
            // If the View size changes, the onSizeChanged method is called insidesizeChange(newWidth, newHeight, oldWidth, oldHeight); }... }// Returns whether the change has occurred
    return changed;
}
Copy the code

The setFrame() method does the following:

  1. Judge whether the View position changes, and according to the change of the corresponding processing;
  2. Initialize the mLeft, mBottom, mRight, and mTop variables. The first time this method is called, the return value istrue;
  3. Call the native methods in RenderNode to update the display list for rendering.

Go back to the layout() method and use the state returned by setFrame() to determine whether onLayout() needs to be called for relayout. Check the onLayout() method:

/** * Assign a size and position to a view and all of its * descendants * * 

This is the second phase of the layout mechanism. * (The first is measuring). In this phase, each parent calls * layout on all of its children to position them. * This is typically done using the child measurements * that were stored in the measure pass().

* *

Derived classes should not override this method. * Derived classes with children should override * onLayout. In that method, they should * call layout on each of their children.

*/
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {}Copy the code

The onLayout() method is an empty method. As you can see from the last paragraph of the comment, a single View does not need to override this method. When a View subclass has child elements (i.e. viewgroups), you should override this method and call layout() for each child element. So as a ViewGroup, we look at the DecorView’s onLayout() method:

@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    super.onLayout(changed, left, top, right, bottom); . }Copy the code

The main logic here is to call the parent’s onLayout() method and continue tracing FrameLayout’s onLayout() method:

@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    layoutChildren(left, top, right, bottom, false /* no force left gravity */);
}

void layoutChildren(int left, int top, int right, int bottom, boolean forceLeftGravity) {
    final int count = getChildCount();

    final int parentLeft = getPaddingLeftWithForeground();
    final int parentRight = right - left - getPaddingRightWithForeground();

    final int parentTop = getPaddingTopWithForeground();
    final int parentBottom = bottom - top - getPaddingBottomWithForeground();

    for (int i = 0; i < count; i++) {
        final View child = getChildAt(i);
        if(child.getVisibility() ! = GONE) {final LayoutParams lp = (LayoutParams) child.getLayoutParams();

            final int width = child.getMeasuredWidth();
            final int height = child.getMeasuredHeight();

            int childLeft;
            int childTop;

            int gravity = lp.gravity;
            if (gravity == -1) {
                gravity = DEFAULT_CHILD_GRAVITY;
            }

            final int layoutDirection = getLayoutDirection();
            final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
            final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK;

            switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
                case Gravity.CENTER_HORIZONTAL:
                    childLeft = parentLeft + (parentRight - parentLeft - width) / 2 +
                    lp.leftMargin - lp.rightMargin;
                    break;
                case Gravity.RIGHT:
                    if(! forceLeftGravity) { childLeft = parentRight - width - lp.rightMargin;break;
                    }
                case Gravity.LEFT:
                default:
                    childLeft = parentLeft + lp.leftMargin;
            }

            switch (verticalGravity) {
                case Gravity.TOP:
                    childTop = parentTop + lp.topMargin;
                    break;
                case Gravity.CENTER_VERTICAL:
                    childTop = parentTop + (parentBottom - parentTop - height) / 2 +
                    lp.topMargin - lp.bottomMargin;
                    break;
                case Gravity.BOTTOM:
                    childTop = parentBottom - height - lp.bottomMargin;
                    break;
                default:
                    childTop = parentTop + lp.topMargin;
            }
            // Call the layout method for each child elementchild.layout(childLeft, childTop, childLeft + width, childTop + height); }}}Copy the code

In the layoutChildren() method, you calculate the left, top, right, and bottom values for each child element based on your layout logic and call their Layout () method.

The Layout process is used by the ViewGroup to determine the position of its child elements. Once the position of the ViewGroup is determined, the onLayout() method iterates through and calls the Layout() method of all child elements. The onLayout() method of the child element is called when the layout() method of the child element is called, thus implementing layers of recursion.

Draw process analysis

Finally, back to the main performTraversals() method, where the size of each View is determined by the Measure process and the position of each View is determined by the Layout process. Now we move on to the next flow to determine the exact drawing details of each View. View the performDraw() method contents:

private void performDraw(a) {...final boolean fullRedrawNeeded = mFullRedrawNeeded;
    mFullRedrawNeeded = false;

    mIsDrawing = true;
    try {
        draw(fullRedrawNeeded);
    } finally {
        mIsDrawing = false; }... }Copy the code

Trace draw() method:

private void draw(boolean fullRedrawNeeded) {...// "dirty" areas, i.e. areas that need to be redrawn
    finalRect dirty = mDirty; .if (fullRedrawNeeded) {
        mAttachInfo.mIgnoreDirtyState = true;
        dirty.set(0.0, (int) (mWidth * appScale + 0.5 f), (int) (mHeight * appScale + 0.5 f)); }...if(! dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) {if(mAttachInfo.mHardwareRenderer ! =null && mAttachInfo.mHardwareRenderer.isEnabled()) {
            ...
            // Call drawSoftware
            if(! drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) {return; }}}... }Copy the code

Check the drawSoftware() method implementation:

private boolean drawSoftware(Surface surface, AttachInfo attachInfo,...) {
    finalCanvas canvas; . canvas = mSurface.lockCanvas(dirty); . mView.draw(canvas); . mSurface.unlockCanvasAndPost(canvas); }Copy the code

The mView is a DecorView, and the mSurface is a Surface object in the Java layer. The actual operation is mapped to the Surface object in the Native layer. Let’s see how the Canvas object is pulled out:

public Canvas lockCanvas(Rect inOutDirty){... mLockedObject = nativeLockCanvas(mNativeObject, mCanvas, inOutDirty);return mCanvas;
}
Copy the code

MNativeObject is the pointer of the Surface object of the Native layer. Here, a Canvas object is obtained from the Surface object of the Native layer through the Native method nativeLockCanvas method. The Canvas object is then passed to the DecorView.

Continue looking at the draw() method in DecorView:

@Override
public void draw(Canvas canvas) {
    super.draw(canvas);

    if(mMenuBackground ! =null) { mMenuBackground.draw(canvas); }}Copy the code

Draw () {FrameLayout (); ViewGroup () {draw();

public void draw(Canvas canvas) {
    final int privateFlags = mPrivateFlags;
    final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&
            (mAttachInfo == null| |! mAttachInfo.mIgnoreDirtyState); mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;/* * Draw traversal performs several drawing steps which must be executed * in the appropriate order: * * 1. Draw the background * 2. If necessary, save the canvas' layers to prepare for fading * 3. Draw view's content * 4. Draw children * 5. If necessary, draw the fading edges and restore layers * 6. Draw decorations (scrollbars for instance) */

    // Step 1, draw the background, if needed
    int saveCount;

    if(! dirtyOpaque) { drawBackground(canvas); }// skip step 2 & 5 if possible (common case)
    final int viewFlags = mViewFlags;
    booleanhorizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) ! =0;
    booleanverticalEdges = (viewFlags & FADING_EDGE_VERTICAL) ! =0;
    if(! verticalEdges && ! horizontalEdges) {// Step 3, draw the content
        if(! dirtyOpaque) onDraw(canvas);// Step 4, draw the children
        dispatchDraw(canvas);

        // Overlay is part of the content and draws beneath Foreground
        if(mOverlay ! =null && !mOverlay.isEmpty()) {
            mOverlay.getOverlayView().dispatchDraw(canvas);
        }

        // Step 6, draw decorations (foreground, scrollbars)
        onDrawForeground(canvas);

        // we're done...
        return;
    }

    /* * Here we do the full fledged routine... * (this is an uncommon case where speed matters less, * this is why we repeat some of the tests that have been * done above) */

    boolean drawTop = false;
    boolean drawBottom = false;
    boolean drawLeft = false;
    boolean drawRight = false;

    float topFadeStrength = 0.0 f;
    float bottomFadeStrength = 0.0 f;
    float leftFadeStrength = 0.0 f;
    float rightFadeStrength = 0.0 f;

    // Step 2, save the canvas' layers
    int paddingLeft = mPaddingLeft;

    final boolean offsetRequired = isPaddingOffsetRequired();
    if (offsetRequired) {
        paddingLeft += getLeftPaddingOffset();
    }

    int left = mScrollX + paddingLeft;
    int right = left + mRight - mLeft - mPaddingRight - paddingLeft;
    int top = mScrollY + getFadeTop(offsetRequired);
    int bottom = top + getFadeHeight(offsetRequired);

    if (offsetRequired) {
        right += getRightPaddingOffset();
        bottom += getBottomPaddingOffset();
    }

    final ScrollabilityCache scrollabilityCache = mScrollCache;
    final float fadeHeight = scrollabilityCache.fadingEdgeLength;
    int length = (int) fadeHeight;

    // clip the fade length if top and bottom fades overlap
    // overlapping fades produce odd-looking artifacts
    if (verticalEdges && (top + length > bottom - length)) {
        length = (bottom - top) / 2;
    }

    // also clip horizontal fades if necessary
    if (horizontalEdges && (left + length > right - length)) {
        length = (right - left) / 2;
    }

    if (verticalEdges) {
        topFadeStrength = Math.max(0.0 f, Math.min(1.0 f, getTopFadingEdgeStrength()));
        drawTop = topFadeStrength * fadeHeight > 1.0 f;
        bottomFadeStrength = Math.max(0.0 f, Math.min(1.0 f, getBottomFadingEdgeStrength()));
        drawBottom = bottomFadeStrength * fadeHeight > 1.0 f;
    }

    if (horizontalEdges) {
        leftFadeStrength = Math.max(0.0 f, Math.min(1.0 f, getLeftFadingEdgeStrength()));
        drawLeft = leftFadeStrength * fadeHeight > 1.0 f;
        rightFadeStrength = Math.max(0.0 f, Math.min(1.0 f, getRightFadingEdgeStrength()));
        drawRight = rightFadeStrength * fadeHeight > 1.0 f;
    }

    saveCount = canvas.getSaveCount();

    int solidColor = getSolidColor();
    if (solidColor == 0) {
        final int flags = Canvas.HAS_ALPHA_LAYER_SAVE_FLAG;

        if (drawTop) {
            canvas.saveLayer(left, top, right, top + length, null, flags);
        }

        if (drawBottom) {
            canvas.saveLayer(left, bottom - length, right, bottom, null, flags);
        }

        if (drawLeft) {
            canvas.saveLayer(left, top, left + length, bottom, null, flags);
        }

        if (drawRight) {
            canvas.saveLayer(right - length, top, right, bottom, null, flags); }}else {
        scrollabilityCache.setFadeColor(solidColor);
    }

    // Step 3, draw the content
    if(! dirtyOpaque) onDraw(canvas);// Step 4, draw the children
    dispatchDraw(canvas);

    // Step 5, draw the fade effect and restore layers
    final Paint p = scrollabilityCache.paint;
    final Matrix matrix = scrollabilityCache.matrix;
    final Shader fade = scrollabilityCache.shader;

    if (drawTop) {
        matrix.setScale(1, fadeHeight * topFadeStrength);
        matrix.postTranslate(left, top);
        fade.setLocalMatrix(matrix);
        p.setShader(fade);
        canvas.drawRect(left, top, right, top + length, p);
    }

    if (drawBottom) {
        matrix.setScale(1, fadeHeight * bottomFadeStrength);
        matrix.postRotate(180);
        matrix.postTranslate(left, bottom);
        fade.setLocalMatrix(matrix);
        p.setShader(fade);
        canvas.drawRect(left, bottom - length, right, bottom, p);
    }

    if (drawLeft) {
        matrix.setScale(1, fadeHeight * leftFadeStrength);
        matrix.postRotate(-90);
        matrix.postTranslate(left, top);
        fade.setLocalMatrix(matrix);
        p.setShader(fade);
        canvas.drawRect(left, top, left + length, bottom, p);
    }

    if (drawRight) {
        matrix.setScale(1, fadeHeight * rightFadeStrength);
        matrix.postRotate(90);
        matrix.postTranslate(right, top);
        fade.setLocalMatrix(matrix);
        p.setShader(fade);
        canvas.drawRect(right - length, top, right, bottom, p);
    }

    canvas.restoreToCount(saveCount);

    // Overlay is part of the content and draws beneath Foreground
    if(mOverlay ! =null && !mOverlay.isEmpty()) {
        mOverlay.getOverlayView().dispatchDraw(canvas);
    }

    // Step 6, draw decorations (foreground, scrollbars)
    onDrawForeground(canvas);
}
Copy the code

As you can see from the code comments, the draw() process is divided into six steps:

  1. Draw the View background;
  2. Save the current canvas layer;
  3. Draw the contents of the View;
  4. If there are child elements, the draw() method of the child element is called;
  5. Draw the fade-in effect and restore the layer;
  6. Draw the View’s decorations (scroll bars, etc.).

Step 2 and step 5 are not normally used, so let’s move on to step 3 and see how it allocates the drawing of child elements:

protected void dispatchDraw(Canvas canvas) {}Copy the code

Obviously, a single View has no child elements, so let’s see how a ViewGroup does this:

@Override
protected void dispatchDraw(Canvas canvas) {...for (int i = 0; i < childrenCount; i++) {
        while (transientIndex >= 0 && mTransientIndices.get(transientIndex) == i) {
            final View transientChild = mTransientViews.get(transientIndex);
            if((transientChild.mViewFlags & VISIBILITY_MASK) == VISIBLE || transientChild.getAnimation() ! =null) { more |= drawChild(canvas, transientChild, drawingTime); }... }... }... }protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
    return child.draw(canvas, this, drawingTime);
}
Copy the code

In the ViewGroup dispatchDraw(), each child element is iterated over and their draw() method is called, thus implementing the layers of recursive calls to finish the drawing.

summary

Throughout the whole process of Measure, Layout and Draw, the flow chart is shown as follows:

Step 8: Display the View

If you remember, the View Measure, Layout, and Draw processes performed in the previous step are all derived from the wm.addView() method in the previous handleResumeActivity(). Review the handleResumeActivity() method:

final void handleResumeActivity(IBinder token,
            boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) { ActivityClientRecord r = mActivities.get(token); .// Methods such as onStart() and onResume() are eventually called
    r = performResumeActivity(token, clearHide, reason);

    if(r ! =null) {
        finalActivity a = r.activity; .if (r.window == null && !a.mFinished && willBeVisible) {
            r.window = r.activity.getWindow();
            / / get DecorView
            View decor = r.window.getDecorView();
            // Make the DecorView invisible
            decor.setVisibility(View.INVISIBLE);
            // Get ViewManager, here is WindowManagerImpl instanceViewManager wm = a.getWindowManager(); WindowManager.LayoutParams l = r.window.getAttributes(); a.mDecor = decor; l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION; l.softInputMode |= forwardBit; .if(a.mVisibleFromClient && ! a.mWindowAdded) {// Flag set to true
                a.mWindowAdded = true;
                // Call WindowManagerImpl's addView methodwm.addView(decor, l); }}else if (!willBeVisible) {
            ...
        }
        ...
        if(! r.activity.mFinished && willBeVisible && r.activity.mDecor ! =null && !r.hideForNow) {
            ...
            if (r.activity.mVisibleFromClient) {
                // Call the makeVisible method to make the DecorView visibler.activity.makeVisible(); }}... }else {
        try {
            // If an exception occurs during this process, the Activity is killed
            ActivityManagerNative.getDefault()
                .finishActivity(token, Activity.RESULT_CANCELED, null,
                        Activity.DONT_FINISH_TASK_WITH_ACTIVITY);
        } catch (RemoteException ex) {
            throwex.rethrowFromSystemServer(); }}}Copy the code

You can see that the DecorView is invisible until the wm.AddView () method is called, so even after the Measure, Layout, and Draw processes, our View is still not displayed on the screen. Take a look at the Activity’s makeVisible() method:

void makeVisible(a) {
    if(! mWindowAdded) { ViewManager wm = getWindowManager(); wm.addView(mDecor, getWindow().getAttributes()); mWindowAdded =true;
    }
    mDecor.setVisibility(View.VISIBLE);
}
Copy the code

After executing DecorView setVisibility(), our View will officially appear on the screen!

conclusion

  • Window is an abstract class that provides methods for various Window operations;
  • PhoneWindow is the only implementation of Window, and there is an instance of PhoneWindw in each Acitvity;
  • DecorView — Top level view that inherits from FrameLayout. When setContentView(), PhoneWindow creates and associates a DecorView with the DecorView;
  • PhoneWindow parses and adds the layout file to the DecorView based on Theme, Feature, etc. These layouts contain a FrameLayout with an ID of Content.
  • The layout file set in the setContentView() method is parsed by PhoneWindow and pressed into the FrameLayout id of Content in the DecorView;
  • The formal rendering of the View begins with the performTraversals() method of the ViewRootImpl;
  • Before a DecorView associates with the WMS, a requestLayout operation is performed to prepare the DecorView for receiving events.
  • RequestLayout calls WMS’s relayoutWindow method with Binder to map Surface objects in the Java layer to Native layer.
  • A single View generally needs to rewrite the onMeasure() method to calculate its own width and height according to the layout parameters and the parent View’s measurement specifications and save;
  • The ViewGroup needs to override the onMeasure() method to calculate the size of all its children and then calculate its own size and save it;
  • A single View generally does not need to override the onLayout() method;
  • The ViewGroup needs to override the onLayot() method to determine the positions of all child elements based on the measured values;
  • The Canvas argument of the View’s onDraw method is returned from the Surface object in the Native layer;
  • A single View needs to override the onDraw() method to draw itself;
  • The ViewGroup needs to override the onDraw() method to draw itself and traverse the child elements to draw them.
  • The ActivityThread will call activit.makevisible () to make the DecorView visible after the Activity’s onResume() life cycle has been called.

This whole process can be roughly represented as follows:

series

This is what happens when you press the power button — Analysis of the Startup process of the Android system

Android App/Activity startup process analysis

How to draw the content on the screen — How Android View works

Refer to the article

Android application layer View drawing process and source code analysis

(3) custom View Layout process – the most understandable custom View principle series

Android UI display principle summary

An article to understand the relationship between The Android graphics system Surface and SurfaceFlinger

Book an In-depth Understanding of the Android kernel (Volume 1)

If you have any questions or different opinions about the content of this article, please leave a comment and we will discuss it together.