View中的Context是哪里来的

前言

在自定义View中我们经常通过getContext()方法来使用Context获取资源、设置样式等。那么大家有没有想过View中getContext()获取的Context是来自哪里的?接下来就分析View中Context的生成过程。

View的Context

我们都知道在View中有个getContext()方法供开发者使用。

   /**
     * 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() {
        return mContext;
    }
复制代码

在上面的注释中可以看出,通过View中的Context是View正在运行的上下文环境中,并且可以通过Context获取资源和主题等。根据这些信息我们可以判断View中的Context是一个ContextThemeWrapper类型的Context。

View的Context初始化

在获取到上面的信息后,我们可以知道View的Context是一个ContextThemeWrapper类型的Context,并且在View的构造方法中也可以看到Context被赋值。根据这些信息我们可以判断View在初始化的时候Context被赋值,所以接下来从Activity的setContentView()开始分析。

在Activity的setContentView()方法中View会被创建以及绘制。

public void setContentView(@LayoutRes int layoutResID) {
    getWindow().setContentView(layoutResID);
    initWindowDecorActionBar();
}
复制代码

可以看到在Activity的setContentView()方法中调用了Window的setContentView()方法。我们知道这个Window就是PhoneWindow,接下来直接在PhoneWindow中分析setContentView()方法。

public void setContentView(int layoutResID) {
    if (mContentParent == null) {
        installDecor(); //首先初始化DecorView
    } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
        mContentParent.removeAllViews();
    }
    //.........
}
复制代码

可以看到再说上面的代码中首先初始化了DecorView也就是顶级View。接下来看下DecorView是如何初始化的。

private void installDecor() {
    mForceDecorInstall = false;
    if (mDecor == null) {
        mDecor = generateDecor(-1); //创建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); //生成布局
     }
    //........
}

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());
}
复制代码

从上面的代码中可以看到,在创建DecorView的过程中,首先会创建一个DecorContext。这个类型的Context就是DecorView中要使用的Context,可以看到这个Context是可以操作主题的。如果Context是空,就使用Window的Context,而Window的Context来自于Activity。

在DecorView创建完成后,接下来就是生成布局。这里就开始了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);  //生成布局
    //......
    mDecor.finishChanging();
	return contentParent;
}

void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
    mDecorCaptionView = createDecorCaptionView(inflater);
    final View root = inflater.inflate(layoutResource, null);  //初始化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();
}
复制代码

在上面的代码中可以看出,布局是从LayoutInflater的inflate开始的。

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);
            //......
            return result;
        }
    }

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)) {
                //通过tag创建
                parseViewTag(parser, parent, attrs);
            } else if (TAG_INCLUDE.equals(name)) {
                if (parser.getDepth() == 0) {
                    throw new InflateException("<include /> cannot be the root element");
                }
                // include 标签
                parseInclude(parser, context, parent, attrs);
            } else if (TAG_MERGE.equals(name)) {
                throw new InflateException("<merge /> must be the root element");
            } else {
                //创建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);
            }
        }
    }
复制代码

一路跟代码下去,我们可以看到在rInflate()方法中根据布局XML文件开始递归的创建View树。在这个过程中每个View都会被创建。可以看到在createViewFromTag(parent, name, context, attrs)方法中使用了Context的参数,接下来继续分析。

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) {
            //这里为View创建了一个ContextThemeWrapper类型的Context
            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;
                }
            }

            return view;
        } 
    }
复制代码

从上面的代码中我们就找到了答案,View的Context在这里被创建。

总结

在上面的分析中,我们可以知道View中的Context是ContextThemeWrapper类型的。在View被绘制之前的初始化过程中被创建。当然根据Android的特性View的Context也得是一个ContextThemeWrapper类型的,因为View中也涉及到了Theme的操作。

转载于:https://juejin.im/post/5d3723a46fb9a07ede0b8430

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值