preface

In custom views we often use Context to get resources, set styles, etc., using the getContext() method. So have you ever wondered where the Context that getContext() gets in the View is coming from? Now let’s look at how the Context is generated in the View.

The Context of the View

We all know that there is a getContext() method in the View for developers to use.

   /**
     * Returns the context the view is running in, through which it can
     * access the current theme, resources, etc.
     *
     * @return The view's Context.
     */
    @ViewDebug.CapturedViewProperty
    public final Context getContext(a) {
        return mContext;
    }
Copy the code

As you can see from the comments above, the Context in a View is the Context in which the View is running, and you can get resources, topics, etc. From this information we can tell that the Context in the View is of type ContextThemeWrapper.

The View’s Context is initialized

With the above information, we know that the View’s Context is a ContextThemeWrapper Context, and we can also see that the Context is assigned in the View constructor. From this information we can determine that the Context is assigned when the View is initialized, so we’ll start with the Activity’s setContentView().

The View is created and drawn in the Activity’s setContentView() method.

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

You can see that the Window setContentView() method is called in the Activity setContentView() method. We know that this Window is a PhoneWindow, so we’ll analyze the setContentView() method directly in PhoneWindow.

public void setContentView(int layoutResID) {
    if (mContentParent == null) {
        installDecor(); // First initialize the DecorView
    } else if(! hasFeature(FEATURE_CONTENT_TRANSITIONS)) { mContentParent.removeAllViews(); }/ /...
}
Copy the code

As you can see, the above code first initializes the DecorView, which is the top-level View. Let’s take a look at how the DecorView is initialized.

private void installDecor(a) {
    mForceDecorInstall = false;
    if (mDecor == null) {
        mDecor = generateDecor(-1); / / create a DecorView
        mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
        mDecor.setIsRootNamespace(true);
        if(! mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures ! =0) { mDecor.postOnAnimation(mInvalidatePanelMenuRunnable); }}else {
        mDecor.setWindow(this);
    }
    if (mContentParent == null) {
        mContentParent = generateLayout(mDecor); // Generate the layout
     }
    / /...
}

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

As you can see from the above code, in the process of creating a DecorView, a DecorContext is first created. This type of Context is the one to use in the DecorView, and you can see that this Context can manipulate the topic. If the Context is empty, the Window Context is used, and the Window Context comes from the Activity.

After the DecorView is created, the next step is to generate the layout. So this is the initialization of the View.

protected ViewGroup generateLayout(DecorView decor) {
      / /...
    if (mIsFloating) {
        setLayout(WRAP_CONTENT, WRAP_CONTENT);
        setFlags(0, flagsToUpdate);
    } else {
        setFlags(FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR, flagsToUpdate);
    }
    / /...
    mDecor.startChanging();
    mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);  // Generate the layout
    / /...
    mDecor.finishChanging();
	return contentParent;
}

void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
    mDecorCaptionView = createDecorCaptionView(inflater);
    final View root = inflater.inflate(layoutResource, null);  // Initialize the View
    if(mDecorCaptionView ! =null) {
        if (mDecorCaptionView.getParent() == null) {
            addView(mDecorCaptionView,
                 new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
        }
        mDecorCaptionView.addView(root,
            new ViewGroup.MarginLayoutParams(MATCH_PARENT, MATCH_PARENT));
    } else {
        addView(root, 0.new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
    }
    mContentRoot = (ViewGroup) root;
    initializeElevation();
}
Copy the code

As you can see in the code above, the layout starts from the inflate of the LayoutInflater.

public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
        synchronized (mConstructorArgs) {
            / /...
           // Inflate all children under temp against its context.
           rInflateChildren(parser, temp, attrs, true);
            / /...
            returnresult; }}void rInflate(XmlPullParser parser, View parent, Context context,
            AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {

        final int depth = parser.getDepth();
        int type;
        boolean pendingRequestFocus = false;

        while(((type = parser.next()) ! = XmlPullParser.END_TAG || parser.getDepth() > depth) && type ! = XmlPullParser.END_DOCUMENT) {/ /...
            final String name = parser.getName();
            if (TAG_REQUEST_FOCUS.equals(name)) {
                pendingRequestFocus = true;
                consumeChildElements(parser);
            } else if (TAG_TAG.equals(name)) {
                // Create by tag
                parseViewTag(parser, parent, attrs);
            } else if (TAG_INCLUDE.equals(name)) {
                if (parser.getDepth() == 0) {
                    throw new InflateException("<include /> cannot be the root element");
                }
                / / the include tag
                parseInclude(parser, context, parent, attrs);
            } else if (TAG_MERGE.equals(name)) {
                throw new InflateException("<merge /> must be the root element");
            } else {
                / / create the View
                final View view = createViewFromTag(parent, name, context, attrs);
                final ViewGroup viewGroup = (ViewGroup) parent;
                final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
                rInflateChildren(parser, view, attrs, true); viewGroup.addView(view, params); }}}Copy the code

Following the code, you can see that in the rInflate() method you start creating the View tree recursively from the layout XML file. Every View is created during this process. You can see that the createViewFromTag(parent, name, Context, attrs) method uses the context argument.

View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
            boolean ignoreThemeAttr) {
        if (name.equals("view")) {
            name = attrs.getAttributeValue(null."class");
        }

        // Apply a theme wrapper, if allowed and one is specified.
        if(! ignoreThemeAttr) {// A ContextThemeWrapper Context is created for the View
            final TypedArray ta = context.obtainStyledAttributes(attrs, ATTRS_THEME);
            final int themeResId = ta.getResourceId(0.0);
            if(themeResId ! =0) {
                context = new ContextThemeWrapper(context, themeResId);
            }
            ta.recycle();
        }

        if (name.equals(TAG_1995)) {
            // Let's party like it's 1995!
            return new BlinkLayout(context, attrs);
        }
        try {
            View view;
            if(mFactory2 ! =null) {
                view = mFactory2.onCreateView(parent, name, context, attrs);
            } else if(mFactory ! =null) {
                view = mFactory.onCreateView(name, context, attrs);
            } else {
                view = null;
            }

            if (view == null&& mPrivateFactory ! =null) {
                view = mPrivateFactory.onCreateView(parent, name, context, attrs);
            }
            if (view == null) {
                final Object lastContext = mConstructorArgs[0];
                mConstructorArgs[0] = context;
                try {
                    if (-1 == name.indexOf('. ')) {
                        view = onCreateView(parent, name, attrs);
                    } else {
                        view = createView(name, null, attrs); }}finally {
                    mConstructorArgs[0] = lastContext; }}returnview; }}Copy the code

The answer can be found in the code above, where the View Context is created.

conclusion

From the above analysis, we know that the Context in the View is of type ContextThemeWrapper. Created during initialization before the View is drawn. The View Context must be ContextThemeWrapper (ContextThemeWrapper), because the View contains the Theme.