Plug-in Skin Change Framework - setContentView Source Reading

Keywords: Android Windows SDK Google

1. overview

The first phase of the content segment sub-architecture has been updated, and later we mainly focus on google source code. Today I'll show you the source code of setContentView. Please first see if you print a TextView from Activity and a TextView from AppCompatActivity.

Inherit from Activity:   
android.widget.TextView{ac5cd17 V.ED..... ......ID 0,0-0,0 #7f0b002c app:id/text_view}

//Inherited from AppCompatActivity:  
android.support.v7.widget.AppCompatTextView{392562b V.ED..... ......ID 0,0-0,0 #7f0b0055 app:id/text_view}

Who can tell me what's going on here? My layout clearly shows why TextView has become AppCompatTextView since AppCompatActivity, so let's see how the source code actually abducted my TextView.

All sharing outlines: The Way to Advance Android in 2017

Video Address: 8 p.m. on Saturdays

![Uploading GIF_791391.gif …]

2. Activity's setContentView source code reading

2.1 Many people have asked me how to look at the source code, I just want to say how to look at it? Of course, sit in and see it!

    public void setContentView(@LayoutRes int layoutResID) {
        // Get Windows to call the setContentView method of Windows and find that it is an abstract class, so we need to find a specific implementation class PhoneWindow.
        getWindow().setContentView(layoutResID);
    }

    // setContentView Method in Phone Windows
    @Override
    public void setContentView(int layoutResID) {
        // If mContentParent is not empty, call installDecor();
        if (mContentParent == null) {
            installDecor();
        } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            mContentParent.removeAllViews();
        }

        // Add our own layout layoutId to mContentParent, and the layout we set in was Soga in it.
        mLayoutInflater.inflate(layoutResID, mContentParent);
    }

2.2 installDecor(), which I've shown you before, but I can't come in again.
  

    // This is the top-level view of the window, containing the window decor.
    // See this explanation?
    private DecorView mDecor;

    private void installDecor() {    
        if (mDecor == null) {
           // Create a DecorView first 
           mDecor = generateDecor(-1);
        }
        // ......
        // Omit to adjust some code, look dizzy, but this is too much.
        if (mContentParent == null) {
           mContentParent = generateLayout(mDecor);
        }
    }

    // GeneeDecor method
    protected DecorView generateDecor(int featureId) {
        // It's a new DecorView. The source code of different versions of DecorView extends FrameLayout is slightly different.
        // The lower version of DecorView is the internal class of Phone Windows, while the higher version is a separate class, but that doesn't affect it.
        return new DecorView(context, featureId, this, getAttributes());
    }

    protected ViewGroup generateLayout(DecorView decor) {
        // Inflate the window decor.
        // I see what happened to you.
        int layoutResource;
        // It's all judgments that find layoutResource = a resource file of the system.
        if(){}else if(){}else if(){
            // Embedded, so no decoration is needed.
            layoutResource = R.layout.screen_simple;
            // System.out.println("Simple!");
        }

        mDecor.startChanging();
        // Loading layout parsing to DecorView and loading layout is a system-provided layout, different versions are different
        // Some of the sources are addView(), which is actually the same
        mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);

        //  ID_ANDROID_CONTENT is android.R.id.content. This View is found in DecorView.
        //  That is to say, to find an id from the layoutResource of the system is a FrameLayout of android.R.id.content.
        ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);

        // Return
        return contentParent;
    }

In fact, the source code must take the original starting point, or there are too many in it can not find direction at all. If you look with the idea, you can come back again even if you run away. Now I just want to find out where our setContentView() system actually adds our layout. Let me summarize it in words and draw a picture.
- setContentView() is set in Activity, and our layout display is mainly to instantiate a DecorView through PhoneWindow and PhoneWindow.
- Instantiate DecorView, then make a series of judgments and parse the resource layoutId file of the system. As for which resource file will be parsed, such as whether there is a header, etc., load it into DecorView. There is a View in the resource layout whose id is android.R.id.content.
- The layout id we set ourselves through setContentView is actually parsed into mParentContent, which is FarmeLayout with the id called android.R.id.content. That's all.

3. setContentView of AppCompatActivity

    @Override
    public void setContentView(@LayoutRes int layoutResID) {
        // It's totally different from what I saw online.
        getDelegate().setContentView(layoutResID);
    }


    public AppCompatDelegate getDelegate() {
        if (mDelegate == null) {
            mDelegate = AppCompatDelegate.create(this, this);
        }
        return mDelegate;
    }

    // Windows is the same window, just take a look. Different versions return to AppCompat Delegate Impl, but they inherit from each other.
    // The final inheritance is to inherit some versions of AppCompat Delegate Impl V9 and some versions of V7 and V9. Hi!
    private static AppCompatDelegate create(Context context, Window window,
            AppCompatCallback callback) {
        final int sdk = Build.VERSION.SDK_INT;
        if (BuildCompat.isAtLeastN()) {
            return new AppCompatDelegateImplN(context, window, callback);
        } else if (sdk >= 23) {
            return new AppCompatDelegateImplV23(context, window, callback);
        } else if (sdk >= 14) {
            return new AppCompatDelegateImplV14(context, window, callback);
        } else if (sdk >= 11) {
            return new AppCompatDelegateImplV11(context, window, callback);
        } else {
            return new AppCompatDelegateImplV9(context, window, callback);
        }
    }

    // There's nothing good to see below, just go in one by one and look carefully. It's no different from Activity.
    @Override
    public void setContentView(int resId) {
        ensureSubDecor();
        ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);
        contentParent.removeAllViews();
        LayoutInflater.from(mContext).inflate(resId, contentParent);
        mOriginalWindowCallback.onContentChanged();
    }

    private void ensureSubDecor() {
        mSubDecor = createSubDecor();
    }

4. AppCompatViewInflater source code analysis

I still don't know why my TextView turned into AppCompat TextView, so I found this way:

    @Override
    public void installViewFactory() {
        LayoutInflater layoutInflater = LayoutInflater.from(mContext);
        if (layoutInflater.getFactory() == null) {
            // Setting the Factory of LayoutInflater to this means that creating View later will follow its own onCreateView method
            // If you don't understand the source code of LayoutInflater, our LayoutInflater.from(mContext) is actually a single case.
            // If Factory is set, the onCreateView method of Factory is executed every time a View is created.
            LayoutInflaterCompat.setFactory(layoutInflater, this);
        } else {
            if (!(LayoutInflaterCompat.getFactory(layoutInflater)
                    instanceof AppCompatDelegateImplV7)) {
                Log.i(TAG, "The Activity's LayoutInflater already has a Factory installed"
                        + " so we can not install AppCompat's");
            }
        }
    }

    @Override
    public View createView(View parent, final String name, @NonNull Context context,
            @NonNull AttributeSet attrs) {
        // See if 5.0, 5.0 have their own effects, I will not say?
        final boolean isPre21 = Build.VERSION.SDK_INT < 21;

        if (mAppCompatViewInflater == null) {
            mAppCompatViewInflater = new AppCompatViewInflater();
        }

        // We only want the View to inherit its context if we're running pre-v21
        final boolean inheritContext = isPre21 && shouldInheritContext((ViewParent) parent);
        // Create View through AppCompatViewInflater
        return mAppCompatViewInflater.createView(parent, name, context, attrs, inheritContext,
                isPre21, /* Only read android:theme pre-L (L+ handles this anyway) */
                true, /* Read read app:theme as a fallback at all times for legacy reasons */
                VectorEnabledTintResources.shouldBeUsed() /* Only tint wrap the context if enabled */
        );
    }

In fact, we've seen the source code of AppCompatViewInflater once about database optimization. It's all a reflection to create View, but it's only a cache and optimization. We can actually copy the source code to give us a good idea.

public final View createView(View parent, final String name, @NonNull Context context,
            @NonNull AttributeSet attrs, boolean inheritContext,
            boolean readAndroidTheme, boolean readAppTheme, boolean wrapContext) {

        View view = null;
        // If I found you, Haha, I did the replacement.
        // We need to 'inject' our tint aware Views in place of the standard framework versions
        switch (name) {
            case "TextView":
                view = new AppCompatTextView(context, attrs);
                break;
            case "ImageView":
                view = new AppCompatImageView(context, attrs);
                break;
            case "Button":
                view = new AppCompatButton(context, attrs);
                break;
            case "EditText":
                view = new AppCompatEditText(context, attrs);
                break;
            // .........
        }

        if (view == null && originalContext != context) {
            // If the original context does not equal our themed context, then we need to manually
            // inflate it using the name so that android:theme takes effect.
            view = createViewFromTag(context, name, attrs);
        }
        return view;
    }

private View createView(Context context, String name, String prefix)
            throws ClassNotFoundException, InflateException {
        // Get it from the construct cache first
        Constructor<? extends View> constructor = sConstructorMap.get(name);
        try {
            if (constructor == null) {
                // Class not found in the cache, see if it's real, and try to add it
                Class<? extends View> clazz = context.getClassLoader().loadClass(
                        prefix != null ? (prefix + name) : name).asSubclass(View.class);
                // Create a constructor with reflection
                constructor = clazz.getConstructor(sConstructorSignature);
                sConstructorMap.put(name, constructor);
            }
            constructor.setAccessible(true);
            // Create an instance of View using reflection
            return constructor.newInstance(mConstructorArgs);
        } catch (Exception e) {
            // We do not want to catch these, lets return null and let the actual LayoutInflater
            // try
            return null;
        }
}

All sharing outlines: The Way to Advance Android in 2017

Video Address: 8 p.m. on Saturdays

Posted by chaking on Thu, 18 Apr 2019 15:45:33 -0700