preface

The existing View system of Android is a very large structure system. It is certainly impossible to cover everything in this article, but I will try my best to show you the intuitive system as much as possible.

The View of system

structure

At present, Android View system is based on View and ViewGroup two categories, while ViewGroup is a subclass of View. Its structural design is based on the “composite mode”, ViewGroup is the container, View is the leaf node, which means that a ViewGroup can contain both View and ViewGroup, but not vice versa, which ensures the security of dependencies.

Composition patterns are not covered in this article, but will be covered in a series of future articles on design patterns. Interested students can take a look at them first.

So the tree structure of the View looks like this:

For those of you who have worked on Android, when we write a layout file, viewGroups and Views are nested, so how do they show up on the screen?

As you can see, the View structure is a Tree. Due to the nature of trees, the display process of views is drawn step by step from top to bottom (the actual process is of course very complicated). It can be said that every View Activity contains a View Tree to describe the View relationship displayed on the screen. This allows developers to better locate each View.

Also, the View’s starting point is not an XML View file defined by us. It is covered by a layer provided by the system. What is this layer?

The top-level layout

This section is about the source code analysis, if you are not interested in the students can skip first, first directly give the conclusion:

We all know that we need to implement the setContentView method in our Activity’s onCreate method to bind our defined layout. Where is the binding layout bound? Is it tied directly to the Activity?

The answers to these questions naturally need to be found in the source code, which is based on Android 10.

Entrance to analysis

Our coding typically starts with an Activity, and we have an onCreate method for initialization. Remember setContentView(r.layout.activity_main); ? This is where we set up the XML layout and the entry point for our analysis.

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

The creation process

1. The setContentView method of the Activity

The proxy is entered first:

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

If you dig into the setContentView under getDelegate(), you’ll find that it’s an abstract method, and lose the thread so quickly? Once you think of the word proxy, look for an object that implements this method.

The comment on the abstract method also indicates where to look:

/**
 * Should be called instead of {@link Activity#setContentView(int)}}
 */
public abstract void setContentView(@LayoutRes int resId);
Copy the code

Since new activities are ultimately inherited from the Acivity class, we find the setContentView method implemented in the Activty class:

// The Activity's setContentView method
public void setContentView(@LayoutRes int layoutResID) {
    // Get Window, set layout --> analyze 2
    getWindow().setContentView(layoutResID);
    // Initialize the ActionBar
    initWindowDecorActionBar();
}

// There are three overloaded functions in the Activity to create layouts in different situations:
// setContentView(@LayoutRes int layoutResID)
// setContentView(View view)
// setContentView(View view, ViewGroup.LayoutParams params)
Copy the code

You can see that there are two steps in the Activity’s setContentView method, which literally means:

  • Get the Window and set the layout
  • Initialize the ActionBar

There are two questions:

  • What Window did you get?
  • Where is the initializing ActionBar, what is the Decor?

Let’s address our questions one by one:

Analysis 2. Where to get the Window and how to set the layout

The getWindow method returns an mWindow object through code tracing:

public Window getWindow(a) {
    return mWindow;
}
Copy the code

So where is this mWindow instance created from? A search shows that this is a PhoneWindow

// mWindow is an instance object of PhoneWindow
mWindow = new PhoneWindow(this, window, activityConfigCallback);
Copy the code

So we know that we are using PhoneWindow to set the layout, so let’s go to PhoneWindow and search for setContentView method:

@override public void setContentView(int layoutResID) { If empty, create a DecorView --> Analysis 3. If (mContentParent == null) {installDecor(); } // Add the layout to the parent of the content --> show the process mLayOutinflater.inflate (layoutResID, mContentParent); // mContentParent. AddView (view, params); Method // The principle is the same, but the parameters obtained are different and need to be displayed in a different way // because it will eventually be added through the addView(View, params) method, the display process will take this method as the entrance...}Copy the code

Ps: The PhoneWindow class is hidden in the source code and cannot be accessed directly through code tracing. You can read the source code in the SDK.

Analyze the process of creating a DecorView

Go to the installDecor method and take a look at how the DecorView object is created.

// Omit extraneous code and focus only on the creation of the DecorView
private void installDecor(a) {
    // Determine if the DecorView is empty. If it is empty, create the DecorView
    if (mDecor == null) {
        / / create a DecorView
        mDecor = generateDecor(-1);
        mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
        mDecor.setIsRootNamespace(true);
        if(! mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures ! =0) { mDecor.postOnAnimation(mInvalidatePanelMenuRunnable); }}// If not empty, attach the DecorView to the PhoneWindow
    else {
        mDecor.setWindow(this);
    }
    
    // If the content parent layout is empty, the layout is generated according to the DecorView
    if (mContentParent == null) {
        // Get the content layoutmContentParent = generateLayout(mDecor); ...}}Copy the code

There are two methods, generateDecor and generateLayout, which are used to create and generate decorViews. What’s going on here?

The generateDecor method creates a DecorView and returns a DecorView instance after obtaining the parameters needed by the DecorView


protected DecorView generateDecor(int featureId) {
    Context context;
    if (mUseDecorContext) {
        Context applicationContext = getContext().getApplicationContext();
        if (applicationContext == null) {
            context = getContext();
        } else {
            context = new DecorContext(applicationContext, getContext());
            if(mTheme ! = -1) { context.setTheme(mTheme); }}}else {
        context = getContext();
    }
    return new DecorView(context, featureId, this, getAttributes());
}
Copy the code

The generateLayout method primarily implements the creation of a layout in a DecorView

Protected ViewGroup generateLayout(DecorView decor) {··· // Layout resources int layoutResource; LayoutResource = r.layout.screen_simple; layoutResource = r.layout.screen_simple; // And loaded into the DecorView mDecor. OnResourcesLoaded (mLayoutInflater, layoutResource); ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT); ... return contentParent; }Copy the code

Now that we have a DecorView, one question leads to another: What does the layout look like in the DecorView?

Let’s go to the file for r.layout. screen_simple and see what the layout looks like.

<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

You can see that the DecorView contains a LinearLayout that contains two parts of the actionBar and FrameLayout (which will be replaced by the defined layout file).

We can draw a picture like this:

The entire nesting relationship is clearly displayed: Activity–>PhoneWindow–>DecorView–>LinearLayout–> Contains the ActioneBar and the layout view defined

Once you’ve created your view, it’s time to display it, so let’s talk about the presentation process.

Show the process

Back in the PhoneWindow setContent method, we looked at how the DecorView was created, and now let’s look at how the DecorView is displayed once it is created.

@override public void setContentView(int layoutResID) { If empty, create the DecorView --> previous analysis. If (mContentParent == null) {installDecor(); } // Add the layout to the parent of the content --> show the process mLayOutinflater.inflate (layoutResID, mContentParent); // mContentParent. AddView (view, params); Method // The principle is the same, but the parameters obtained are different and need to be displayed in a different way // because it will eventually be added through the addView(View, params) method, the display process will take this method as the entrance...}Copy the code

Display process will eventually be drawn through the addView method of ViewGroup display, in this part of the content we will enter the View of the drawing process to go.

public void addView(View child, int index, LayoutParams params) {...// Request layout --> analyze 4
    requestLayout();
    // Invalidate?? The word "invalidate" means invalidate --> analyze 5. Is it really invalidation?
    invalidate(true);
    addViewInner(child, index, params, false);
}
Copy the code
Analyze 4. Request layout process

The core method is mparent.requestLayout (); The call:

// View requestLayout method
public void requestLayout(a) {
    
    if(mParent ! =null && !mParent.isLayoutRequested()) {
        // Call the requestLayout method of ViewRootImplmParent.requestLayout(); }...}Copy the code

There’s a new variable called mParent, so what is it? The ViewRootImpl implementation class is ViewRootImpl, and the ViewRootImpl implementation class is ViewRootImpl requestLayout.

// ViewRootImpl requestLayout method
public void requestLayout(a) {
    if(! mHandlingLayoutInLayoutRequest) {// Check whether the thread is in UI thread
        checkThread();
        mLayoutRequested = true;
        // Walk through the table --> analyze 6scheduleTraversals(); }}Copy the code

For scheduleTraversals, remember the invalidate method used with requestLayout? Let’s analyze it before we forget it.

Analysis 5. Is it really invalidation?

After requestLayout (); The code executes to invalidate(true); When I first saw this method name, I was confused. What does invalidation mean? Since do not understand can only take this question to see the source.

// The invalidateCache parameter is named invalidateCache
public void invalidate(boolean invalidateCache) {
    // To do this, pass in some parameters related to the size
    invalidateInternal(0.0, mRight - mLeft, mBottom - mTop, invalidateCache, true);
}
Copy the code

Seeing the parameter name, I wonder if this is a process of invalidating cached draw data and redrawing it?

Let’s move on to the invalidateInternal method, which is just one line of core code.

void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache,
        boolean fullInvalidate) {...// Invalid child control, old version of the source code is this line of code, marked as obsolete
    p.invalidateChild(this, damage);
    
    // The new version uses another method, as follows
    receiver.damageInParent();
}
Copy the code

Enter the damageInParent method:

// View method
protected void damageInParent(a) {
    if(mParent ! =null&& mAttachInfo ! =null) {
        mParent.onDescendantInvalidated(this.this); }}Copy the code

Here need to pay attention to the directly into the mParent onDescendantInvalidated (this, this); Remember that mParent’s implementation class ViewRootImpl, let’s go into this method to see what its implementation is:

@Override public void onDescendantInvalidated(@NonNull View child, @NonNull View descendant) { // TODO: Re-enable after camera is fixed or consider targetSdk checking this // checkThread(); if ((descendant.mPrivateFlags & PFLAG_DRAW_ANIMATION) ! = 0) { mIsAnimating = true; } invalidate(); }Copy the code

Invalidate () = invalidate() = invalidate() = invalidate() = invalidate()

void invalidate(a) {
    mDirty.set(0.0, mWidth, mHeight);
    if(! mWillDrawSoon) {// Walk through the table --> analyze 6scheduleTraversals(); }}Copy the code

Emmm, this has come full circle to the scheduleTraversals method, so let’s go in and see what happens in the final method.

Analyze 6. Final process
void scheduleTraversals(a) {
    if(! mTraversalScheduled) { mTraversalScheduled =true;
        mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
        // Note the mTraversalRunnable variable, which passes an operation
        mChoreographer.postCallback(
                Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); notifyRendererOfFramePending(); pokeDrawLockIfNeeded(); }}Copy the code

Note the mChoreographer. PostCallback (Choreographer. CALLBACK_TRAVERSAL mTraversalRunnable, null); And the mTraversalRunnable variable of, is passing some operation so let’s see what is done.

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

The code traces the corresponding implementation into the method:

void doTraversal(a) {
    if (mTraversalScheduled) {
        mTraversalScheduled = false;
        mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);

        if (mProfile) {
            Debug.startMethodTracing("ViewAncestor");
        }

        // Where the actual traversal takes place, which is the entrance to the later View drawing process
        performTraversals();

        if (mProfile) {
            Debug.stopMethodTracing();
            mProfile = false; }}}Copy the code

Now that we’ve traced the code here, we have a pretty good idea of what setContentView is going to do, and then we’re going to go through the process of measuring and drawing the View, which we’ll talk about in a future article.

Drawing process

Although on the View of the drawing process, this article does not expand, but on the View of the drawing process or to talk about. Drawing a View can be divided into three parts:

  • measurement
  • layout
  • draw

The three processes are the View displayed on the screen, which is also easy to understand, each step corresponds to a general design step.

The source code analysis of the various steps and processes of View drawing will be covered in a later article.

function

To implement a View, you need to handle the following but more than the following events, which will be covered in future articles.

  • Create a View
  • measurement
  • layout
  • draw
  • The event processing
  • Focus on processing
  • According to processing
  • Animation processing

conclusion

This article mainly introduced the View of system structure, and in the View of drawing done before the operation, the part of source analysis, and not very detailed, just on the part of one of the main content, there are a lot of other content (judge jump animation, thread) no, because the article focus is different, If you’re interested, you can look it up for yourself and maybe talk about it in the future.