This is the 10th day of my participation in the August More Text Challenge

(1) The relationship between Activity,Window and View

View the rendering process from the Activity’s setContent method (2)

View the rendering process from the setContent method of the Activity (3) and then view the Window

See the rendering process from the setContent method of an AppCompatActivity

AppCompatActivity

Currently, nativity is almost anything that inherits AppCompatActivity for compatibility. Some people may have found that clicking on the setContent method of AppCompatActivity is completely different from that of an Activity. So the difficulty of all the stuff we analyzed before is discarded? Offe Snoot.

Whether based on Android5.0 or Android 11.0, the source will be different, but the core code, the central idea, will not change anything. Let’s look at the differences between setContent and Activity for AppCompatActivity

Visual observation hierarchy

Change the existing inherited Activity to inherit AppCompatActivity and rerun the project. Open the LayoutInspector

It’s not the same as the original. The original hierarchy is the one below

There are four levels of activities, there are six levels of appactivities that are clearly different so compare them layer by layer and see what the differences are.

  1. The outermost layer is still the DecorView. I’m relieved to see that if the first layer is different, then the relationship between the DecorView and the Window will all have to be repeated.
  2. The second layer is the same, because the theme is the same, so the outer layer is the LinearLayout
  3. The third layer is the same, but not exactly the same. It’s the same actionBar+FrameLayout style, which makes sense because the second layer uses the same XML. The difference is that FrameLayout has an ID and is a familiar content. Now it has no ID
  4. The fourth layer looks completely different, a FitWindowsLinearLayout with id action_bar_root. Look at the name is also LinearLayout
  5. The fifth layer is different, but not completely different. It must be different because there was no fifth level before, but look at the level.
  • Similarity 1: The ViewStubCompat with the ID of the action_mode_bar_stub type and the third-layer ViewStub with the ID of the Action_mode_bar_stub type. I thought it was very similar. ViewStubCompat is compatible with ViewStubCompat.

  • Similarity 2: The layout below is ContentFrameLayout, which is definitely a subclass of FrameLayout. The most important thing is that its ID is Content. And let’s look at my next layer, it’s also the parent container for our own XML. Isn’t the difficulty worth thinking about?

  1. The sixth layer is our own XML, exactly like the original fourth layer.

After comparing, I can see that AppCompatActivity has two more layers nested in FrameLayout with the original ID of content. And the new 4 and 5 floors. Very similar to the original 2,3 floors. Then the new ID for Content becomes the parent layout of our XML at the fifth layer.

At first I was puzzled by his strange layout, but I knew he must have done it for compatibility. Secondly, I really want to know how the content got to the fifth level. Because from the analysis, he didn’t change the original 1-3 layers, just added two more layers to the original level, but why did the original content go to the fifth level?

Code demonstrates

androidx.appcompat.app.AppCompatActivity

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

GetDelegate () looks like it’s delegating to a class implementation, familiar with the delegate pattern. I suggest that you first learn about 24 design patterns and then look at the source code, because both official and well-known third-party libraries use a lot of design patterns, familiar with design patterns can better understand the source code ideas, but also can deepen the understanding of design patterns again.

    @NonNull
    public AppCompatDelegate getDelegate() {
        if (mDelegate == null) {
            mDelegate = AppCompatDelegate.create(this, this);
        }
        return mDelegate;
    }
Copy the code

Look at what the Create of the AppCompatDelegate returns.

androidx.appcompat.app.AppCompatDelegate

    public static AppCompatDelegate create(@NonNull Activity activity,
            @Nullable AppCompatCallback callback) {
        return new AppCompatDelegateImpl(activity, callback);
    }
Copy the code

Ok, the concrete implementation class is found: AppCompatDelegateImpl

androidx.appcompat.app.AppCompatDelegateImpl

    @Override
    public void setContentView(int resId) {
        ensureSubDecor();
        ViewGroup contentParent = mSubDecor.findViewById(android.R.id.content);
        contentParent.removeAllViews();
        LayoutInflater.from(mContext).inflate(resId, contentParent);
        mAppCompatWindowCallback.getWrapped().onContentChanged();
    }
Copy the code

There are several overloads of setContentView on AppCompatDelegateImpl, we just need to look at the XML layout of the parameters, others are the same. SetContentView does three things.

  1. Create initial SubDecor. We know that ensureDecor is initialized for DecorView creation. This is the creation initialization of the child DecorView. (subDecorView, I don’t know what it is, but think of the view analysis above, you should have a guess.)

  2. Find the container with the id content, empty out all the original child views, and add the XML that we wrote to it (this is familiar)

  3. Execute the Activity’s onContentChanged callback.

The third step is not to say more, look at the code can also guess what to do, click on your own can determine, the second step is not to say more, put our XML in the ID of content should have been a matter of course. So it’s all in the first step, ensureSubDecor.

ensureSubDecor

androidx.appcompat.app.AppCompatDelegateImpl

ViewGroup mSubDecor;
private void ensureSubDecor() {
    if (!mSubDecorInstalled) {
      mSubDecor = createSubDecor();
    }
}
Copy the code

Internal implement createSubDecor, createSubDecor this method is longer and more important. I decided to subsidize the code, and the images below are the original code. Analyze it piece by piece.

The most important ones are the two red boxes of code that show some handling of the Window based on the Settings. The same is true for the previous analysis of the DecorView. The first red box assigns a value to the following variable, mWindow, which is the Phonew instance of the current Activity. The code is posted at the end of this article mWindow.getDecorView()Internally, this method executes a familiar methodinstallDecor

com.android.internal.policy.PhoneWindow

    @Override
    public final @NonNull View getDecorView() {
        if (mDecor == null || mForceDecorInstall) {
            installDecor();
        }
        return mDecor;
    }
Copy the code

PhoneWindow setContent method, PhoneWindow setContent method, PhoneWindow setContent method. The interior generateDecor method and generateLayout make the view hierarchy clear, and PhoneWindow’s setContent method is where the Activity’s setContent really starts. So the AppCompatActivity setContent method doesn’t call PhoneWindow setContent right now, but instead has its core installDecor() code executed first.

Then look at the code behind ensureSubDecor

Also familiar, generateLayout of PhoneWindow matches the XML file used in the DecorView by theme. Here is the matching XML file for subDecor. I’m going to go ahead and click on any of them and you’re going to find a different layout but I’m going to include a layout file called abc_screen_content_include, so I’m going to go in

And found one in itContentFrameLayout with id action_bar_activity_content. In the previous analysis, our layout was added to the ContentFrameLayout container, but other people’sId is the contentThis one is calledaction_bar_activity_contentFor nothing.

Then look at the code behind ensureSubDecor

There was an exception when the subDecor was null. The subDecor should be XML.

Then see ensureSubDecor

This is a lot of code. Don’t worry. Let’s look at it line by line.

In red box 1: First, I found the ContentFrameLayout with id action_bar_activity_content in the subDecor I just saw and defined the variable as contentView. Then in our familiar DecorView we find the container with the ID as Content and define the variable as windowContentView.

Red box 2: Iterate over the windowContentView, remove all of its layout, and add it to the contentView.

Red box 3: Empty the windowContentView ID and set the contentView ID to Content. (To be honest, all I thought was a big “Oh my God.”)

Red box 4: Set *contentView to foreground

Red box 5: Execute PhoneWindow’s setContView method with subDecor as an argument. (oh my god. Oh my god. Oh my god)

  • I don’t know if you’re aware of what’s going on, but PhoneWindow’s setContView method, which we analyzed in the first article, is where the Activity actually executes setContent, Now he takes our subDecor as a View and implements PhoneWindow’s setContView method. Although we haven’t put our XML into subDecor yet, we know from the analysis at the beginning of this article that our XML will eventually be in subDecor.
  • You might think that the FrameLayout ID in the DecorView with the ID content has been erased, but remember that we started by calling the installDecor method of PhoneWinow, At this point, the internal mContentParent has been referenced, and the id erasure does not affect subsequent operations.

Red box 6: Return to subDecor. At this point, subDecor has been created and initialized.

conclusion

AppCompatActivity can be compatible by creating something SubDeorView in the form of creating DercorView and then nesting the XML we write into it. Set the SubDeorView to the FrameLayout of the original Content in the DercorView. (Because the FrameLayout ID is erased later and the SubDecorView next container ID is set to content)

You can even think of wrapping our XML in a SubDecorView and then calling the Activity’s setContent method (loosely, but you can imagine).

The AppCompatActivity setContent does a graft operation inside. It’s still called setContent, but it’s not the same content anymore.

How to assign mWindow under AppCompatDelegateImpl

Make sure that the mWindow variable has a reference to the Phonew currently held by the Activity. The constructor for creating AppCompatDelegateImpl has an activity in it and internally just gets the PhoneWdidow object from the activity’s getWindow. The following figure