我们知道,每个界面的样式都可以使用xml文件来设置。但是为什么可以这样,android系统又是怎么将xml文件转化为布局的呢?
首先,使用inflate是这样来用的:
LayoutInflater.from(this).inflate(R.layout.activity_main,null);
这句话做了什么呢?我们先看from(context)方法:
/** * Obtains the LayoutInflater from the given context. */ public static LayoutInflater from(Context context) { LayoutInflater LayoutInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); if (LayoutInflater == null) { throw new AssertionError("LayoutInflater not found."); } return LayoutInflater; }
可以看到,这个方法是获取了系统的叫做LAYOUT_INFLATER_SERVICE的服务。
那么inflate方法做了什么呢?
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) { return inflate(resource, root, root != null); }
他会调一个重载方法,并且第三个参数是判断第二个参数不为空。也就是root不为空
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) { final Resources res = getContext().getResources(); final XmlResourceParser parser = res.getLayout(resource); //拿到xml解析器 try { return inflate(parser, root, attachToRoot); //parse xml解析器, root 根布局 attachRoot 根布局不为空 } finally { parser.close(); } }这个方法会通过资源文件拿到xml解析器,然后再掉用重载方法。
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 { //找到根节点 int type; while ((type = parser.next()) != XmlPullParser.START_TAG && type != XmlPullParser.END_DOCUMENT) { // Empty } //如果执行到这的根节点不是Start标签,就抛异常 if (type != XmlPullParser.START_TAG) { throw new InflateException(parser.getPositionDescription() + ": No start tag found!"); } final String name = parser.getName(); //获取节点的名称 if (TAG_MERGE.equals(name)) { //处理merge 标签 //代码省略 } else { final View temp = createViewFromTag(root, name, inflaterContext, attrs); //将节点名解析成对应的view ViewGroup.LayoutParams params = null; if (root != null) { params = root.generateLayoutParams(attrs); //将属性解析成layoutParams if (!attachToRoot) { temp.setLayoutParams(params); //将参赛设置给新创建出来的view } } rInflateChildren(parser, temp, attrs, true); //解析子布局,第二个参数是新建好的view,要注意下。 if (root != null && attachToRoot) { root.addView(temp, params); //将新的view添加到根布局中 } if (root == null || !attachToRoot) { //root 为空 那么 attachToRoot一定是false result = temp; } } } catch (rException e) { //异常处理,省略了很多异常 final InflateException ie = new InflateException(e.getMessage(), e); ie.setStackTrace(EMPTY_STACK_TRACE); throw ie; } return result; } }
具体的流程注释已经比较清楚的体现了。就是,先找到根节点,如果没有找到就抛异常。然后就是调用createViewFromTag 创建对应的view出来,然后在解析子view。这里注意下,如果我们调用inflate的时候root传的null,那么最后会将构建好的布局传回去。如果root不为空,那么就走的是root.addView(temp),会将新建好的布局添加到根布局中去。
接下来看看createViewFromTag 这个方法做了些什么:
View createViewFromTag(View parent, String name, Context context, AttributeSet attrs, boolean ignoreThemeAttr) { if (name.equals("view")) { //处理新建的view就是View的情况 name = attrs.getAttributeValue(null, "class"); } // Apply a theme wrapper, if allowed and one is specified. if (!ignoreThemeAttr) { //是否要忽略主题的属性,视乎都是false的 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)) { //这里有故事啊,这个tag是blink,特殊处理 // 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); //解析系统控件,其实最后也是调用的createView } else { view = createView(name, null, attrs); //解析自定义控件,第二个参数为null } } finally { mConstructorArgs[0] = lastContext; } } return view; } catch (InflateException e) { //异常处理省略了很多 throw e; } }
这里其实就做了这么几件事:
是否要解析主题参数。
是否按照我们自定义的方式去解析布局,也就是说支持自己鞋解析方式。
判断是否是系统的控件,或者是自定义控件,然后分别解析成view。
那么,系统的控件有事怎么解析的呢?
protected View onCreateView(String name, AttributeSet attrs) throws ClassNotFoundException { return createView(name, "android.view.", attrs); }
其实就是将 android.view.前缀加上去调用解析自定义控件的方式解析,也就是控件的包全名,然后调用createView去解析。在上面解析自定义控件的时候第二个参数传的是 null。接下来看看createView做了什么:
public final View createView(String name, String prefix, AttributeSet attrs) throws ClassNotFoundException, InflateException { //省略部分代码 Class<? extends View> clazz = null; try { Trace.traceBegin(Trace.TRACE_TAG_VIEW, name); // 这里会判断第二个参数不为空时和name拼起来去加载对应的类, clazz = mContext.getClassLoader().loadClass( prefix != null ? (prefix + name) : name).asSubclass(View.class); if (mFilter != null && clazz != null) { boolean allowed = mFilter.onLoadClass(clazz); if (!allowed) { failNotAllowed(name, prefix, attrs); } } constructor = clazz.getConstructor(mConstructorSignature); constructor.setAccessible(true); sConstructorMap.put(name, constructor); //省略部分代码 Object lastContext = mConstructorArgs[0]; if (mConstructorArgs[0] == null) { // Fill in the context if not already within inflation. mConstructorArgs[0] = mContext; } Object[] args = mConstructorArgs; args[1] = attrs; final View view = constructor.newInstance(args); //通过反射去新建view if (view instanceof ViewStub) { //特殊处理ViewStub // Use the same context when inflating ViewStub later. final ViewStub viewStub = (ViewStub) view; viewStub.setLayoutInflater(cloneInContext((Context) args[0])); } mConstructorArgs[0] = lastContext; return view; }catch (Exception e) { //异常处理省略了很多 final InflateException ie = new InflateException( attrs.getPositionDescription() + ": Error inflating class " + (clazz == null ? "<unknown>" : clazz.getName()), e); ie.setStackTrace(EMPTY_STACK_TRACE); throw ie; } finally { Trace.traceEnd(Trace.TRACE_TAG_VIEW); } }
这个地方就是view最终生成的地方,是通过反射来做的。反射的类名如果prefix不为空,会将其和name拼起来,在解析系统布局的时候用到的。还有一个方法,创建子view的是啥样的呢?
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) { if (type != XmlPullParser.START_TAG) { continue; } final String name = parser.getName(); if (TAG_REQUEST_FOCUS.equals(name)) { //处理requestFocus pendingRequestFocus = true; consumeChildElements(parser); } else if (TAG_TAG.equals(name)) { //处理tag parseViewTag(parser, parent, attrs); } else if (TAG_INCLUDE.equals(name)) { //处理 include if (parser.getDepth() == 0) { throw new InflateException("<include /> cannot be the root element"); } parseInclude(parser, context, parent, attrs); } else if (TAG_MERGE.equals(name)) { //处理merge,因为merge只能在根节点中创建,所以这里直接抛异常了 throw new InflateException("<merge /> must be the root element"); } else { final View view = createViewFromTag(parent, name, context, attrs); //调用上面的方法创建view final ViewGroup viewGroup = (ViewGroup) parent; final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs); rInflateChildren(parser, view, attrs, true); //继续解析子view viewGroup.addView(view, params); //将新建的view加到父布局中 } } if (pendingRequestFocus) { parent.restoreDefaultFocus(); } if (finishInflate) { parent.onFinishInflate(); } }
从这里可以看出,解析子view的时候是按深度优先的方式去遍历布局树的(递归调用自己)。
从整体上看出,inflate的过程就是从根布局开始,按深度优先的规则去解析xml文件里面的view节点,新建view,在将view添加到其父布局中,最终构成了一个view树。