在Android中LayoutInflater用的地方有很多,它的作用就是将用xm编写的布局文件转换为View对象。今天我们就来分析它的实现原理。
我们知道在Activity中我们向其中添加布局是通过setContentView(@LayoutRes int layoutResID)这个方法来添加的,我们今天就从这个地方开始一步一步分析它是怎么将xml描述的view添加到我们的Activity窗口,知道显示在用户眼前的。我们先进入setContentView方法,方法定义如下
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
我们看到它调用了Window的setContentView,但是Window类中对应的方法是抽象方法,那么它在哪里实现的呢。稍微对源码有些了解的人都知道,Window类的实现类是PhoneWindow,因此我们进入PhoneWindow类的对应的方法,看看其中的方法,实现如下:
@Override
public void setContentView(int layoutResID) {
// Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
// decor, when theme attributes and the like are crystalized. Do not check the feature
// before this happens.
if (mContentParent == null) {
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
mContentParent.removeAllViews();
}
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
getContext());
transitionTo(newScene);
} else {
mLayoutInflater.inflate(layoutResID, mContentParent);
}
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
mContentParentExplicitlySet = true;
}
在方法中如果没有设置场景动画,它会走 mLayoutInflater.inflate(layoutResID, mContentParent);这个方法。而mLayoutInflater是PhoneLayoutInflater对象,它是LayoutInflater对象的子类。我们进入inflate方法,定义如下:
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
return inflate(resource, root, root != null);
}
我们发现如果传入的root不为null,第三个参数就为true。由上面我们可以知道root是不为null的。我们接着往下走:
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
final Resources res = getContext().getResources();
if (DEBUG) {
Log.d(TAG, “INFLATING from resource: \”” + res.getResourceName(resource) + “\” (”
+ Integer.toHexString(resource) + “)”);
}
final XmlResourceParser parser = res.getLayout(resource);
try {
return inflate(parser, root, attachToRoot);
} finally {
parser.close();
}
}
我们看到这个方法主要做了两件事情,一件是创建XmlResourceParser对象,一看就知道是个XML解析实现类,第二个就是调用inflate方法,它的定义如下:
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(parser, root, inflaterContext, attrs, false);
} else {
// Temp is the root view that was found in the xml
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) {
final InflateException ie = new InflateException(e.getMessage(), e);
ie.setStackTrace(EMPTY_STACK_TRACE);
throw ie;
} catch (Exception e) {
final InflateException ie = new InflateException(parser.getPositionDescription()
+ ": " + e.getMessage(), e);
ie.setStackTrace(EMPTY_STACK_TRACE);
throw ie;
} finally {
// Don't retain static reference on context.
mConstructorArgs[0] = lastContext;
mConstructorArgs[1] = null;
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
return result;
}
}
这个方法有点长,这个主要是解析XML,分为XML的名称是否为merge两种情况来解析。
下面我们就分这两种情况来分析。
第一种情况就是XML标签名称等于merge时候:
当传入的root(根View)为null和attachToRoot(是否添加到根View)为false其中就抛出异常,否则进入如下方法:
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)) {
pendingRequestFocus = true;
consumeChildElements(parser);
} 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 (pendingRequestFocus) {
parent.restoreDefaultFocus();
}
if (finishInflate) {
parent.onFinishInflate();
}
}
由上面方法我们知道它解析Xml分如下五种情况来执行。分别是Xml标签名称为requestFocus,tag,include,merge和其它标签名称这物种情况。
当为requestFocus时候:
首先将pendingRequestFocus置为true,再进入下面这个方法:
final static void consumeChildElements(XmlPullParser parser)
throws XmlPullParserException, IOException {
int type;
final int currentDepth = parser.getDepth();
while (((type = parser.next()) != XmlPullParser.END_TAG ||
parser.getDepth() > currentDepth) && type != XmlPullParser.END_DOCUMENT) {
// Empty
}
}
我们看到这里面就对XML进行遍历,直到遍历到requestFocus的内部xml,就跳出循环(目的就是接着遍历它的子XML)。
而pendingRequestFocus置为true有什么作用呢,我们看看如下定义,
if (pendingRequestFocus) {
parent.restoreDefaultFocus();
}
由此我们知道了,它是将当前解析的父Viwe即parent请求获取焦点。
当XML标签名称等于tag的时候:
会执行如下方法
private void parseViewTag(XmlPullParser parser, View view, AttributeSet attrs)
throws XmlPullParserException, IOException {
final Context context = view.getContext();
final TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.ViewTag);
final int key = ta.getResourceId(R.styleable.ViewTag_id, 0);
final CharSequence value = ta.getText(R.styleable.ViewTag_value);
view.setTag(key, value);
ta.recycle();
consumeChildElements(parser);
}
我们看到这个方法就是获取当前View的id和对应的tag值,将id作为键区域,将tag作为值区域将这个tag保存到当前View中。consumeChildElements这个方法就是将xml解析到末尾处。
当XML标签值是include的时候:
如果它是作为顶层View的时候,会抛出“ cannot be the root element”异常,
否则会进入到parseInclude这个方法,由于这个方法比较长,在这里我就不贴出来了。就简单介绍下这个方法主要作用:
第一,保证include标签必须是ViewGroup或者是它的子类;
第二,如果设置了主题,就初始化ContextThemeWrapper这个对象;
第三,通过已经初始化了的ContextThemeWrapper对象来获取include的XML文件;
第四,解析XML布局文件,并且为它生成对应的布局参数即LayoutParams对象,将解析的子View添加到父View(parent)中去。
如果XML标签值是merge时候抛出异常,提示“ must be the root element”;
否则通过类加载器来加载类并且调用其构造方法来进行初始化,并为其生成设置LayoutParams。再循环解析初始化被它所包围的子View。
最后如果root不为null并且attachToRoot为false,就为生成的view设置LayoutParams
如果root不为null并且attachToRoot为true的时候,将生成的LayoutParams对象设置到新生成的View中并且将新生成的View添加到根布局中,最后将添加了新生成的View的根布局返回。
如果root为null或者attachToRoot为false就将新生成View(没有设置LayoutParams)返回。
至此LayoutInflater实现原理分析完毕了。