Source code analysis | appcompatactivity setcontentview I was careless

HZWZ

Now young people stick the source code as soon as they come up. Is this suitable for a small vegetable melon like me? It's not suitable.

background

This is how the story begins

  • One day, I found that I didn't write the layout
  • Print as I want
  • With an inexplicable beginning
  • One day, two young people didn't talk about martial virtue
  • You have to tell me this is the reason for AppCompatActivity
  • I don't believe it
  • Their sneak attack was clearly well prepared
  • I was careless
  • I didn't flash
  • Today, I want to prove myself
  • The third generation of the code of the mixed yuan sect, a working cow. See Synonyms at

Familiar taste

Why? It's an ordinary TextView. Why did it become a MaterialTextView? Are you teasing me.

track down sb. by following clues

Worker, worker soul, I'm Hunyuan gate

Bah, it's off the point. We're on track. We have to pick your underpants today.

Enter setcontentview() of AppCompatActivity first:

public void setContentView(@LayoutRes int layoutResID) {
    getDelegate().setContentView(layoutResID);
}

Boring, go to appcompatdelegateimpl setcontentview()

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

It's very direct here. Students who have seen activity setcontentview know what contentParent is. As for our root layout, it's needless to say that the later inflate mainly lies in ensuesubdecor(), and see what's done in it.

Enter ensuesubdecor()

private void ensureSubDecor() {
    if (!mSubDecorInstalled) {
        mSubDecor = createSubDecor();
      	...Omit a large piece of code
    }
}

mSubDecorInstalled means whether DecorView has been installed in windows. If it has been installed, it will be ignored.

Why would I know? Translation, ohhh.

// true if we have installed a window sub-decor layout.
private boolean mSubDecorInstalled;

Enter createSubDecor()

    private ViewGroup createSubDecor() {
      	//1
        TypedArray a = mContext.obtainStyledAttributes(R.styleable.AppCompatTheme);
      	if(xxx)xxx
      	//2
        ensureWindow();
      	//3
        mWindow.getDecorView();
				...//Ignore a piece of code
        ViewGroup subDecor = null;
      
      	//4 
         subDecor = (ViewGroup) inflater.inflate(R.layout.abc_screen_simple, null);
				//5
        final ContentFrameLayout contentView = (ContentFrameLayout) subDecor.findViewById(
                R.id.action_bar_activity_content);
        final ViewGroup windowContentView = (ViewGroup) mWindow.findViewById(android.R.id.content);
      	
      	//6
        if (windowContentView != null) {
            // There might be Views already added to the Window's content view so we need to
            // migrate them to our content view
            while (windowContentView.getChildCount() > 0) {
                final View child = windowContentView.getChildAt(0);
                windowContentView.removeViewAt(0);
                contentView.addView(child);
            }
            windowContentView.setId(View.NO_ID);
            contentView.setId(android.R.id.content);
        }

        //7
        mWindow.setContentView(subDecor);
        return subDecor;
    }

This method is relatively complex. Once you understand it, you can end this article.

Stage small thinking

The question is, why should all views in phonewindows windows content be copied to the new contentView in the source code?

Because the default DecorView of Activity loads r.layout screen_ Simple * * *, and our root layout is one of the sub FrameLayout. When using AppCompatActivity, it has its own corresponding theme layout for compatibility. Therefore, when setting, first put all the sub views in the current root container into the new container, and then set the ID of this container to R.id.content *, that is, make it a new root container, Then add this container to the root layout (i.e. FrameLayout) in our DecorView, so as to achieve the compatibility effect without affecting the display of the original view.

Do you understand that?

Don't you understand? Well, let me say it again from the background

As mentioned above, AppCompatActivity has its own specific container layout. If it directly replaces the default root container of the Activity during design, it means that AppCompatActivity must write a copy independently. Is this appropriate? Obviously not. Therefore, because of this, the DecorView variable name in AppCompatActivity is called mSubDecor, while that in our basic PhoneWindows is called mDecor. Think about why a so-called DecorView is defined separately in AppCompatActivity. What's the meaning, and cooperate with the above operations in appcompatdelegateimpl createsubdecor() method. Now do you understand?

See the following figure for details:

By checking the level, we will find that the original AppCompatActivity is nested on the original Activity layout level. As described above, does it feel like ohhh, that's it.

String ideas

  1. When we call setContentView() in AppCompatActivity, the internal call is setContentView() of AppCompatDelegateImpl, and finally calls ensuesubdecor (), which is used to ensure whether the DecorView has been initialized successfully.
  2. In the ensuesubdecor () method, first judge whether the child DecorView (why it is a child, because it cannot directly replace the root DecorView,AppCompatActivity only makes a compatibility, that is, add a sub level on the DecorView to shield the caller, which is actually unaware of the user). If not, Then call createSubDecor() to initialize it;
  3. createSubDecor() will be internally configured according to the current theme, and finally set the current root container, add all child views in the current windows decorview root container FrameLayout to the new container, change the ID of the new container to R.id.content, and then windows Setcontentview(), i.e. * * * add * * * to the old container FrameLayou, becoming the only child container. This new container is the latest root container.
  4. The subsequent method is very simple. Our own layout can be directly add ed to the root container ViewGroup.

Why is the print inconsistent

Wait, what is the reason for adding prefix in the first View printing, and what is the relationship between this and setContentView()?

Yes, it doesn't seem to matter. Then you say xxx here. Sorry, let's switch to the next topic.

Let's go back to the beginning of appcompatactivity create()

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    final AppCompatDelegate delegate = getDelegate();
  	//entrance
    delegate.installViewFactory();
    delegate.onCreate(savedInstanceState);
    super.onCreate(savedInstanceState);
}
There's nothing to say. Let's go directly to installViewFactory()
@Override
public void installViewFactory() {
    LayoutInflater layoutInflater = LayoutInflater.from(mContext);
  	//1
    if (layoutInflater.getFactory() == null) {
        LayoutInflaterCompat.setFactory2(layoutInflater, this);
    }
  	...
}

If we don't set the layoutinflator factory, the default factory will be set, and then the onCreateView() method will be called when finally creating the layout.

We enter the corresponding onCreateView()

@Override
public View createView(View parent, final String name, @NonNull Context context,
        @NonNull AttributeSet attrs) {
  	...
    return mAppCompatViewInflater.createView(parent, name, context, attrs, inheritContext,IS_PRE_LOLLIPOP,true,VectorEnabledTintResources.shouldBeUsed()
    );
}

There's nothing to say. Go to mappcompatviewinflator createView()

final View createView(View parent, final String name, @NonNull Context context,
        @NonNull AttributeSet attrs, boolean inheritContext,
        boolean readAndroidTheme, boolean readAppTheme, boolean wrapContext) {
		...
    switch (name) {
        case "TextView":
            view = createTextView(context, attrs);
            verifyNotNull(view, name);
            break;
        ...

Oh, ha ha, it turns out that our default View has been replaced here, which is why we use AppCompatActivity to print out the child View with its own prefix display.

Added by $0.05$ on Thu, 10 Feb 2022 05:25:19 +0200