View drawing process

setContentView

If you’re familiar with the Activity startup process, you’ll be familiar with the handleLaunchActivity method in the sequence diagram ActivityThread. This method is called to execute performLaunchActivity after the Activity is started. Create an Activity object through reflection inside performLaunchActivity.

//ActivityThread#performLaunchActivity
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
    Activity activity = null;
    java.lang.ClassLoader cl = appContext.getClassLoader();
    // Create an Activity object with reflection in the newActivity method of Instrumentation
    activity = mInstrumentation.newActivity(cl, component.getClassName(), r.intent);
    
    // Execute the Activity's performCreate method
    mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
    
    return activity;
}

//Instrumentation#newActivity
public Activity newActivity(ClassLoader cl, String className,Intent intent){
    return (Activity)cl.loadClass(className).newInstance();
}

//Instrumentation#callActivityOnCreate
public void callActivityOnCreate(Activity activity, Bundle icicle) {
    prePerformCreate(activity);
    // Call the Activity's performCreate method and internally continue the callback to the Activity's onCreate method
    activity.performCreate(icicle);
    postPerformCreate(activity);
}
Copy the code

Call callActivityOnCreate in Instrumentation after the Activity object is created, Instrumentation can be seen as a “management class” responsible for monitoring the creation of activities and life cycle callback and startup, etc., in this function to continue to call the Activity of the performCreate method is actually called to the onCreate method, When we write the layout we pass in the layout file in onCreate through the setContentView, and inside the setContentView we’re actually calling the PhoneWindow setContentView.

public void setContentView(int layoutResID) {
    
    if (mContentParent == null) {
        // Code 1: Initialize the top-level parent container DecorView
        installDecor();
    } else if(! hasFeature(FEATURE_CONTENT_TRANSITIONS)) { mContentParent.removeAllViews(); }// Code 2: Add our own layout file to mContentParent
    mLayoutInflater.inflate(layoutResID, mContentParent);
    
  / /...
}

//PhoneWindow#installDecor
private void installDecor(a){
    
    if (mDecor == null) {
        / / code 3
        mDecor = generateDecor(-1);
    }

    if(mContentParent == null) {/ / code 4mContentParent = generateLayout(mDecor); }}Copy the code

In PhoneWindow#setContentView code 1, the installDecor method is called first. In installDecor, generateDecor and generateLayout are implemented. The generateDecor method in Code 3 initializes a DecorView. A DecorView is the top-level View in the Android View architecture, which is essentially a ViewGroup object. The DecorView is passed in generateLayout, which defines a variable, layoutResource, that holds the basic layout under different themes, which is the first layout style you see on IEDA when we create a new project.

//PhoneWindow#generateLayout
protected ViewGroup generateLayout(DecorView decor) {
    // Save the layout file
    int layoutResource;
    // Omit code assigns layoutResource values to different topics
    // Add the layoutResource layout file to the DecorView
    mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
    / / returns the resource id for the com. Android. Internal, R.i, dc ontent View object
    ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
    
    return contentParent;
}
Copy the code

The generateLayout method first defines a variable called layoutResource to hold layout files in different themes. Call DecorView#onResourcesLoaded to add the layoutResource layout file to the DecorView, (ViewGroup) the findViewById (ID_ANDROID_CONTENT) return layoutResource finalised after the control id for the com. Android. The internal, R.i, dc ontent of control object, so far, DecorView is initialized, layoutResource is added to the DecorView, go back to code 2 above and add your own layout file to mContentParent, so at this point your layout file has been parsed, But remember that you don’t find anything here that does layout measurement, layout, and drawing, which is why in onCreate you don’t get the width and height of the View directly, because in setContentView you just parse the layout file and deal with the layout hierarchy, The resulting structure is as follows:

How is a View added to a Window

We’ve looked at how the setContentView layout file we wrote is superimposed on the DecorView and the layout hierarchy of the View, but the onCreate method does not measure, lay, or draw the View object. It’s just dealing with layout parsing, so when are these transactions going to be processed, and what is the process of processing these transactions? The core code is in the ActivityThread’s handleResumeActivity method.

 final void handleResumeActivity(IBinder token,
         boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) 
     
     // Execute the Activity's onResume method
     r = performResumeActivity(token, clearHide, reason);
     if(r ! =null) {
         final Activity a = r.activity;
         booleanwillBeVisible = ! a.mStartedActivity;if (r.window == null && !a.mFinished && willBeVisible) {
             // Get the PhoneWindow object in the Activity
             r.window = r.activity.getWindow();
             // Get the DecorView object according to PhoneWindow
             View decor = r.window.getDecorView();
             decor.setVisibility(View.INVISIBLE);
             // Get WindowManagerImpl, ViewManager is an interface
             ViewManager 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) {
                 if(! a.mWindowAdded) { a.mWindowAdded =true;
                     // Finally call WindowManagerImpl's addView methodwm.addView(decor, l); }}}}//WindowManagerImpl#addView
  public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
     // Bridge mode - WindowManagerGlobal executes the specific addView method
     mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
 }
 
 //WindowManagerGlobal#addView
 public void addView(View view, ViewGroup.LayoutParams params,Display display, Window parentWindow) {
            
      ViewRootImpl root;
      
      synchronized(mLock){
          // Each time a new Activity is created, a ViewRootImpl object is created
         root = new ViewRootImpl(view.getContext(), display);
         view.setLayoutParams(wparams);
         mViews.add(view);
         mRoots.add(root);
         mParams.add(wparams);
         
         try{
             root.setView(view, wparams, panelParentView)
         }catch(RuntimeException e){
            throwe; }}}Copy the code

Perform the detailed drawing process in the handleResumeActivity method, starting by adding the View to the WindowManagerImpl, It uses bridge mode where the specific operations of addView are performed by Windows ManagerGlobal, WindowManagerGlobal is a singleton class that maintains three collections, mViews, mRoots, and mParams, that hold View objects, ViewRootImpl, and some parameters, respectively. The focus is on the setView method of ViewRootImpl, which binds DecorView to ViewRootImpl and performs the measurement, layout, and drawing process in the setView method.

summary

  • Only parsing the layout file and handling the DecorView hierarchy are performed in setContentView

  • HandleResumeActivity executes the View drawing process, and the View drawing process is completed after onResume

  • The addView method via Windows Manager ImpL is ultimately hosted to Windows ManagerGlobal for processing

  • ViewRootImpl is the link between Windows Manager and the DecorView, and all three of the View’s processes are done through ViewRoot. In ActivityThread, after the Activity object is created, Will add the DecorView to the Window and associate the ViewRootImpl object with the DecorView root.setView(View, wParams, panelParentView)

The order in which views are drawn

The setView function of ViewRootImpl is the most important method for us. The drawing process of the View is here. In the setView method, the requestLayout method will be called first. RequestLayout checks whether the current thread is the main thread first, so don’t be surprised to see that onCreate can update the UI in child threads as well, since threads are checked only when onResume is executed. This is followed by executing scheduleTraversals. Here is the core code:

void scheduleTraversals(a) {
    if(! mTraversalScheduled) { mTraversalScheduled =true;
        // Code 1 enables the synchronization barrier
        mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
        // Send asynchronous messages
        mChoreographer.postCallback(
                Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); }}Copy the code

The synchronization barrier is enabled at code 1, followed by sending a TraversalRunnable asynchronous message. If you are familiar with Handler, you will know that the synchronization barrier is designed to increase priority, and the UI interface, as the first “transaction” that the user encounters, must be advanced in order to prevent it from being blocked by other tasks. Execute doTraversal in the run method of the TraversalRunnable:

void doTraversal(a) {
    if (mTraversalScheduled) {
        mTraversalScheduled = false;
        // Remove the synchronization barrier
        mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
        // Execute the specific drawing processperformTraversals(); }}Copy the code

In doTraversal the synchronization barrier must be removed first, otherwise no other ordinary messages will be executed. Where a barrier is added, it must be removed at a reasonable time. PerformTraversals perform a specific rendering process, namely performMeasure, performLayout and performDraw. The three steps of View measurement, layout and rendering are performed respectively. The general process can be shown in the following figure.

PerformMeasure, performLayout and performDraw will be called in performMeasure, performLayout and performDraw in order to complete the top-level View measure, Layout and draw. Taking Measure as an example, onMeasure method will be called in the process of measure, and the measurement process will be transmitted to sub-views in onMeasure, and the process of layout and draw will be recursively completed in turn. The measure process determines the View’s width/height. Once the measure is complete, the measuredWidth and measuredHeight methods can be used to measure the measuredWidth/height of the View. In almost all cases, the measuredWidth and measuredHeight methods are equal to the View’s final width/height. Layout determines the coordinates of the four vertices of the View and the actual width/height of the View. After completion, you can obtain the position of the four vertices of the View by using getTop, getBottom, getLeft, and getRight. You can also use getWidth and getHeight methods to get the final width/height of the View. The Draw process determines the display of the View, and only after the Draw method is complete can the View’s contents appear on the screen.

View.post() implementation principle

You can’t get the width and height of a View directly from onCreate, as explained above, but you can get the width and height of a View by using view.post ().

//View.java
public boolean post(Runnable action) {
    MAttachInfo is assigned to the View's dispatchAttachedToWindow method
    final AttachInfo attachInfo = mAttachInfo;
    
    if(attachInfo ! =null) {
        return attachInfo.mHandler.post(action);
    }
    // The HandlerActionQueue post function
    getRunQueue().post(action);
    return true;
}

//View.java
void dispatchAttachedToWindow(AttachInfo info, int visibility) {
    // Assign mAttachInfo to dispatchAttachedToWindow
    mAttachInfo = info;
    if(mRunQueue ! =null) {
        / / execution HandlerActionQueue# executeActions
        mRunQueue.executeActions(info.mHandler);
        mRunQueue = null; }}Copy the code

AttachInfo is assigned to mAttachInfo in the POST method, and mAttachInfo is assigned to the View’s dispatchAttachedToWindow method, Also execute the executeActions method of the HandlerActionQueue.

public class HandlerActionQueue {
    private HandlerAction[] mActions;
    / / code 1
    public void post(Runnable action) {
        postDelayed(action, 0);
    }
    
    public void postDelayed(Runnable action, long delayMillis) {
        final HandlerAction handlerAction = new HandlerAction(action, delayMillis);
        synchronized (this) {
        if (mActions == null) {
            mActions = new HandlerAction[4]; } mActions = GrowingArrayUtils.append(mActions, mCount, handlerAction); mCount++; }}/ / code 2
    public void executeActions(Handler handler) {
        synchronized (this) {
            final HandlerAction[] actions = mActions;
            for (int i = 0, count = mCount; i < count; i++) {
                final HandlerAction handlerAction = actions[i];
                handler.postDelayed(handlerAction.action, handlerAction.delay);
            }
            mActions = null;
            mCount = 0; }}private static class HandlerAction {
        final Runnable action;
        final long delay;
        public HandlerAction(Runnable action, long delay) {
            this.action = action;
            this.delay = delay; }}}Copy the code

In the View post method if you have attachInfo == null then the HandlerActionQueue post method and finally postDelayed method, In postDelayed encapsulate your task namely the Runnable object and delayMillis delay into the HandlerAction and finally add and save to the mActions array, There’s a class called GrowingArrayUtils that you can see for yourself, which is used to dynamically expand arrays, so to summarize, Post () to get the width and height of the View in onCreate, simply add the runnable task to an array of mActions and save it. MActions supports dynamic expansion. So far we’ve seen that in onCreate attachInfo is null and attachInfo is assigned to the View’s dispatchAttachedToWindow, So where is View#dispatchAttachedToWindow called? In ViewRootImpl# performTraversals will perform in the host. DispatchAttachedToWindow (mAttachInfo, 0). This host is actually a DecorView, which means that the DecorView dispatchAttachedToWindow is executed during the View drawing process, But the DecorView itself doesn’t have this method. Instead, it calls its parent ViewGroup’s dispatchAttachedToWindow. Here’s the code:

void dispatchAttachedToWindow(AttachInfo info, int visibility) {
    / / code 1
    super.dispatchAttachedToWindow(info, visibility);
   
    final int count = mChildrenCount;
    final View[] children = mChildren;
    / / code 2
    for (int i = 0; i < count; i++) {
        finalView child = children[i]; child.dispatchAttachedToWindow(info, combineVisibility(visibility, child.getVisibility())); }}Copy the code

I’m going to call its dispatchAttachedToWindow in code 1 which is to assign a value to the mAttachInfo in the current View, and I’m going to go through all the sub-views and do their dispatchAttachedToWindow in code 2, The AttachInfo we use in the View system is initialized in the ViewRootImpl constructor

mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this,
        context);
Copy the code

Assign its reference to each child control during the dispatchAttachedToWindow traversal. To summarize, The dispatchAttachedToWindow method of the top-level View ~ DecorView is executed when the View’s rendering process goes to the performTraversals method, AttachInfo is initialized in the ViewRootImpl constructor, and the dispatchAttachedToWindow dispatchAttachedToWindow assigns references to this method to each and every child View.

So that explains a lot about how we can get the width and height of a View through view.post, so let’s look at what dispatchAttachedToWindow does for a View, and the code is posted there, It’s basically the executeActions of the HandlerActionQueue that sends the task through the handler to the message queue to be executed, but it’s just a normal message, In scheduleTraversals the synchronization barrier is enabled and the TraversalRunnable of the drawing process is an asynchronous message. The message sent from executeActions is executed only after the synchronization barrier is removed after the drawing process is completed, which is why the View post method is used to get the width and height of the View, since the post task is executed after the drawing process is completed.

AttachInfo: mHandler.post(action); AttachInfo: mhandler. post(action); The handler sends the task to the main thread’s MessageQueue for execution. Of course, at this point, the drawing process of the View must be finished.

conclusion

The onCreate method calls setContentView to determine the layout structure and parse the XML file, but no drawing process is performedThe second step:

The resources

1. Android development art exploration

2, view.post () does what

3. The relationship between Activity, Window and View is understood in this article