这一片主要总结XML布局文件的加载过程,此源码来源于android-23,其它版本大体一致。LayoutInflater被用于加载xml布局文件,在开发中我们经常用它来动态加载xml布局文件。要使用LayoutInflater首先要获取LayoutInflater实例,目前Android提供了三种方式:
查看Activity下的方法getLayoutInflater()
,该方法的最终实现是PhoneWidonw下的getLayoutInflater()
,源码如下:
@Override
public LayoutInflater getLayoutInflater() {
return mLayoutInflater;
}
而此处的mLayoutInflater通过以下方式获得:
public PhoneWindow(Context context) {
super(context);
mLayoutInflater = LayoutInflater.from(context);
}
查看LayoutInflater.from(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;
}
最终我们发现,这三种方式上本质是一样的,最终都是调用context.getSystemService(Context.LAYOUT_INFLATER_SERVICE)。
获取了LayoutInflater的实例之后,便可以通过该实例的inflate()方法来动态加载布局了:
LayoutInflater inflater = LayoutInflater.from(this);
inflater.inflate(resource,root);
inflate()方法有两个参数,第一个是资源文件,第二个是父布局(ViewGroup),如果不需要父布局,则直接传null。该方法多个重载,但最终都是执行方法:
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot)
下面我们来分析该方法。
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 {
// Look for the root node.
int type;
while ((type = parser.next()) != XmlPullParser.START_TAG &&
type != XmlPullParser.END_DOCUMENT) {
// Empty
}
if (type != XmlPullParser.START_TAG) {
throw new InflateException(parser.getPositionDescription()
+ ": No start tag found!");
}
final String name = parser.getName();
if (DEBUG) {
System.out.println("**************************");
System.out.println("Creating root view: "
+ name);
System.out.println("**************************");
}
if (TAG_MERGE.equals(name)) {
if (root == null || !attachToRoot) {
throw new InflateException("<merge /> can be used only with a valid "
+ "ViewGroup root and attachToRoot=true");
}
//根视图被创建之后,通过rInflate()递归创建子视图
rInflate(parser, root, inflaterContext, attrs, false);
} else {
// 通过xml创建根视图,即此处的Temp
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
ViewGroup.LayoutParams params = null;
if (root != null) {
if (DEBUG) {
System.out.println("Creating params from root: " +
root);
}
// Create layout params that match root, if supplied
params = root.generateLayoutParams(attrs);
if (!attachToRoot) {
// Set the layout params for temp if we are not
// attaching. (If we are, we use addView, below)
temp.setLayoutParams(params);
}
}
if (DEBUG) {
System.out.println("-----> start inflating children");
}
// Inflate all children under temp against its context.
rInflateChildren(parser, temp, attrs, true);
if (DEBUG) {
System.out.println("-----> done inflating children");
}
// We are supposed to attach all the views we found (int temp)
// to root. Do that now.
if (root != null && attachToRoot) {
root.addView(temp, params);
}
// Decide whether to return the root that was passed in or the
// top view found in xml.
if (root == null || !attachToRoot) {
result = temp;
}
}
} catch (XmlPullParserException e) {
InflateException ex = new InflateException(e.getMessage());
ex.initCause(e);
throw ex;
} catch (Exception e) {
InflateException ex = new InflateException(
parser.getPositionDescription()
+ ": " + e.getMessage());
ex.initCause(e);
throw ex;
} finally {
// Don't retain static reference on context.
mConstructorArgs[0] = lastContext;
mConstructorArgs[1] = null;
}
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
return result;
}
}
首先以Pull解析方式来解析xml布局文件,然后通过createViewFromTag()
(该方法接受节点名称和节点属性作为参数)来创建view对象。createViewFromTag()
又通过createView()
来创建view,而createView()
是基于反射方式实现的(其实和spring中可以利用xml创建实例一个道理)。
在刚开始时,
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
会首先创建根视图。
接着通过rInflate()
来遍历根视图下的子视图,如下:
void rInflate(XmlPullParser parser, View parent, Context context,
AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {
final int depth = parser.getDepth();
int type;
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)) {
parseRequestFocus(parser, parent);
} else if (TAG_TAG.equals(name)) {
parseViewTag(parser, parent, attrs);
} else if (TAG_INCLUDE.equals(name)) {
if (parser.getDepth() == 0) {
throw new InflateException("<include /> cannot be the root element");
}
parseInclude(parser, context, parent, attrs);
} else if (TAG_MERGE.equals(name)) {
throw new InflateException("<merge /> must be the root element");
} else {
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);
}
}
if (finishInflate) {
parent.onFinishInflate();
}
}
在该方法中的28行同样通过createViewFromTag
来创建View,然后在该方法的31行通过rInflateChildren()
来查找该View的子View,该方法实际上调用了rInflate()
,即该过程是以递归的方式查找view,在每次递归完成后将该view添加到其父View中。
final void rInflateChildren(XmlPullParser parser, View parent, AttributeSet attrs,
boolean finishInflate) throws XmlPullParserException, IOException {
rInflate(parser, parent, parent.getContext(), attrs, finishInflate);
}
整个xml布局文件解析完成后形成一个View Tree,最终把最顶层的根视图返回,止此xml加载过程完成。
上面,我们简单描述了代码的执行过程,但为了更加简单明了,这里我们采用图形来展示.
假如我们现在有以下布局文件:
下面的过程,我们用操作栈和内存视图来简略描述inflate()
的过程,在这个过程中注意栈和内存视图的变化将帮助你更好的理解。
首先来看inflate()
方法中这段代码:
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
该过程创建了根视图root1,并将root1压入栈.
随后执行rInflateChildren()
方法,用来生成root1下子视图.我们注意到该方法实际执行的是rInflate()
方法,接下来我们看rInflate()
方法中的此段代码:
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);
和root1的创建过程类似,通过createViewFromTag()
创建了root2,并将root2压入栈.随后,同样调用rInflateChildren()
方法来创建root2下的子视图.
创建root2下的第一个子视图text1.到目前为止,整个流程如下:
接着创建root2下的第二个子视图text2,此时如下:
到现在为止,root2下的两个子视图text1,text2已经被创建完成,并添加到父视图root2中.
随后root2出栈,root2视图被添加到父视图root1当中.
接下里创建root1下的子视图image.image和root2同级,不同是image下没有子视图.其创建过程和root2类似,此处就不做说明了.
到此为止,整个xml布局被已经被完全加载到内存当中,xml在内存中的布局可描述为以下:
现在我们来回想一下xml文档的结构—-文档树,也就是此时的View Tree
我们用一句话来描述整个加载过程:xml布局加载的过程就是以递归方式创建树的过程
xml布局文件加载的过程演示完了,那么接下来的工作就是如何将这张ViewTree显示出来了,后面我们会用更简单的方式来展示整个View Tree绘制的过程。