在AppCompatActivity的onCreate方法中我们都知道setContentView这个方法是加载布局文件。这个方法使用很简单直接把layout布局文件放进去就可以了。那么具体内部是怎么将它显示到桌面的呢,今天就从setContentView()来解析一下Android中View的创建过程。
打开androidx/appcompat/app/AppCompatActivity.java,这里setContentView就一行代码,调用AppCompatDelegate的setContentView()。
@Overridepublic void setContentView(@LayoutRes int layoutResID) {getDelegate().setContentView(layoutResID);}
AppCompatDelegate是一个抽象类,我们分析一下它的实现类AppCompatDelegateImpl在setContentView()方法中做了什么。
@Overridepublic void setContentView(int resId) {ensureSubDecor();//获取content视图ViewGroup contentParent = mSubDecor.findViewById(android.R.id.content);//移除viewGroup下的所有子ViewcontentParent.removeAllViews();//解析布局文件添加到content视图中LayoutInflater.from(mContext).inflate(resId, contentParent);mAppCompatWindowCallback.getWrapped().onContentChanged();}
打开/frameworks/base/core/java/android/view/LayoutInflater.java查看解析布局文件的LayoutInflater.inflate() 方法:
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {...//获取布局解析器XmlResourceParser parser = res.getLayout(resource);try {return inflate(parser, root, attachToRoot);} finally {parser.close();}}...public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {synchronized (mConstructorArgs) {Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate");final Context inflaterContext = mContext;final AttributeSet attrs = Xml.asAttributeSet(parser);Context lastContext = (Context) mConstructorArgs[0];mConstructorArgs[0] = inflaterContext;View result = root;try {advanceToRootNode(parser);//取得XML标签名字final String name = parser.getName();if (TAG_MERGE.equals(name)) {//如果是merge标签调用rInflate方法,递归执行解析rInflate(parser, root, inflaterContext, attrs, false);} else {// 通过xml的tag来构建Viewfinal View temp = createViewFromTag(root, name, inflaterContext, attrs);ViewGroup.LayoutParams params = null;if (root != null) {//获取布局参数params = root.generateLayoutParams(attrs);if (!attachToRoot) {//设置临时的布局参数temp.setLayoutParams(params);}}//递归执行解析,继续解析temp下的子项,也走到了rInflate()方法rInflateChildren(parser, temp, attrs, true);// 如果root不为空并且attachToRoot为trueif (root != null && attachToRoot) {//将View填充到ViewGrouproot.addView(temp, params);}// 如果root为空 或者 attachToRoot为false,直接返回tempif (root == null || !attachToRoot) {result = temp;}}} catch (XmlPullParserException e) {...}return result;}}
在inflate方法中主要做了一下几步:
1.解析layout.xml文件的根标签
2.判断是否是merge,如果是merge那么调用rInflate()递归方法,实例化视图将View添加到ViewGroup中,然后调用onFinishInflate()。
3.如果不是merge,调用createViewFromTag()来创建view并添加到ViewGroup中,之后调用rInflate()继续递归解析子View;
4.通过attachToRoot,返回视图。
rInflate方法同样也调用createViewFromTag方法,打开createViewFromTag():
View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,boolean ignoreThemeAttr) {...try {View view = tryCreateView(parent, name, context, attrs);if (view == null) {final Object lastContext = mConstructorArgs[0];mConstructorArgs[0] = context;try {//通过.来判断是自定义View还是内置Viewif (-1 == name.indexOf('.')) {//创建内置Viewview = onCreateView(context, parent, name, attrs);} else {//创建自定义Viewview = createView(context, name, null, attrs);}} finally {mConstructorArgs[0] = lastContext;}}return view;} ...}
通过继续跟踪代码,发现自定义View还是内置View,最后都调用了createView方法:
@Nullablepublic final View createView(@NonNull Context viewContext, @NonNull String name,@Nullable String prefix, @Nullable AttributeSet attrs)throws ClassNotFoundException, InflateException {Objects.requireNonNull(viewContext);Objects.requireNonNull(name);//从缓存中获取构造Constructor<? extends View> constructor = sConstructorMap.get(name);if (constructor != null && !verifyClassLoader(constructor)) {constructor = null;sConstructorMap.remove(name);}Class<? extends View> clazz = null;try {if (constructor == null) {//类载入器, 初始化对象clazz = Class.forName(prefix != null ? (prefix + name) : name, false,mContext.getClassLoader()).asSubclass(View.class);if (mFilter != null && clazz != null) {boolean allowed = mFilter.onLoadClass(clazz);if (!allowed) {failNotAllowed(name, prefix, viewContext, attrs);}}//从class中获取构造constructor = clazz.getConstructor(mConstructorSignature);constructor.setAccessible(true);//加入缓存中sConstructorMap.put(name, constructor);} else {...}Object lastContext = mConstructorArgs[0];mConstructorArgs[0] = viewContext;Object[] args = mConstructorArgs;args[1] = attrs;try {//设置attrs属性,通过反射获取View实例final View view = constructor.newInstance(args);if (view instanceof ViewStub) {// Use the same context when inflating ViewStub later.final ViewStub viewStub = (ViewStub) view;viewStub.setLayoutInflater(cloneInContext((Context) args[0]));}return view;} finally {mConstructorArgs[0] = lastContext;}} ...}
-
总结:
- 通过 pull解析XML文件获取到 View 的标签;
- 通过标签中是否有.判断是自定义View还是内置View
- 通过反射的方式来创建 View 对象;
- 通过深度优先的顺序遍历View, 形成View树;