问题
主要问题:
LayoutInflater 是怎么把xml添加到decorview?
衍生问题:
include 为什么不能作为xml资源布局的根节点?
merge 为什么作为xml资源布局的根节点?
先来看setContentView里的 mLayoutInflater.inflate(layoutResID, mContentParent)方法
@Override
public void setContentView(int layoutResID) {
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);
}
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
}
点inflate进去是以下代码,是把我们自定义的xml和ContentParent传进去
public View inflate(int resource, ViewGroup root) {
return inflate(resource, root, root != null);
}
继续往里进入inflate方法,resource是我们自定义的xml,root是我们的ContentParent,第三个参数从命名上看意思是,是否添加倒root布局里,如果root传的是null则为false,反之则为true。往下看重要的方法是–>利用res.getLayout(resource)方法得到xml解析器,把xml文件解析出来,之后是inflate(parser, root, attachToRoot),
public View inflate(int resource, 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();
}
}
我们进入inflate(parser, root, attachToRoot)这个方法继续探索,方法如下
public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot) {
synchronized (mConstructorArgs) {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate");
*//先拿xml的各种属性 *
final AttributeSet attrs = Xml.asAttributeSet(parser);
Context lastContext = (Context)mConstructorArgs[0];
mConstructorArgs[0] = mContext;
**//把root赋值给result**
View result = root;
try {
// Look for the root node. 先去找根节点
int type;
while ((type = parser.next()) != XmlPullParser.START_TAG &&
type != XmlPullParser.END_DOCUMENT) {
// Empty
}
。。。省略若干行
//首先判断根节点是不是merge标签
if (TAG_MERGE.equals(name)) {
if (root == null || !attachToRoot) {
//如果是merge标签,但是 root == null || !attachToRoot就抛出一个异常,因为如果使用merge标签,肯定有得有一个顶层的ViewGroup标签来放这个加载的layout
throw new InflateException("<merge /> can be used only with a valid "
+ "ViewGroup root and attachToRoot=true");
}
如果root不为空就用以下方法把逐层解析添加到root(ViewGroup)里面,这里传的false
rInflate(parser, root, attrs, false, false);
} else {
//当是一些常用的标签的时候,首先会利用createViewFromTag方法来生成我们自定义的根节点,比如LinearLayout标签
// Temp is the root view that was found in the xml
final View temp = createViewFromTag(root, name, attrs, false);
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
//利用父节点的root.generateLayoutParams方法生成子节点的属性(这里的父节点就是id为content的Framelayout)
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()方法,把父节点解析出来的属性设置到子节点上;
temp.setLayoutParams(params);
}
}
if (DEBUG) {
System.out.println("-----> start inflating children");
}
// Inflate all children under temp
//加载所有子节点 这里传的true
rInflate(parser, temp, attrs, true, 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 (IOException 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;
}
}
再看rInflat方法
void rInflate(XmlPullParser parser, View parent, final AttributeSet attrs,
boolean finishInflate, boolean inheritContext) throws XmlPullParserException,
IOException {
final int depth = parser.getDepth();
int type;
//while循环,通过遍历循环来解析child节点,当不是最后一个标签的时候会走以下方法逐层解析,并添加到parent,如果只加载一个View,拿肯定不会走这个方法了
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)) {
//这里是判断include标签,当该布局里面没有东西的时候,会抛出一个异常 inclide标签不能作为根节点
if (parser.getDepth() == 0) {
throw new InflateException("<include /> cannot be the root element");
}
parseInclude(parser, parent, attrs, inheritContext);
} else if (TAG_MERGE.equals(name)) {
//merge只能作为根节点,如果出现在子节点中会抛出异常。
throw new InflateException("<merge /> must be the root element");
} else {
//一层一层遍历解析
final View view = createViewFromTag(parent, name, attrs, inheritContext);
final ViewGroup viewGroup = (ViewGroup) parent;
final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
rInflate(parser, view, attrs, true, true);
viewGroup.addView(view, params);
}
}
//当为merge标签时候 不走这个方法,为普通标签时会走这个方法
//控制回调函数,具体以后在讲
if (finishInflate) parent.onFinishInflate();
}
小结:每一个Activity都有一个关联的Window对象,用来描述应用程序窗口。
每一个窗口内部又包含了一个DecorView对象,Decorview对象用来描述窗口的视图–xml布局,以上大概记录了xml布局的加载过程,
上述是创建DecorView的过程
附上图片更易理解