我们以LayoutInflater类的public View inflate(int resource, ViewGroup root)方法为例;当调用inflate()方法的时候,是对一个xml文件进行加载的,我们可以跟踪这个方法:
inflate(int resource, ViewGroup root)方法会调用同个类里面的inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot)方法,这个方法就是一个很关键的方法,我们可以查看这个方法的代码:
1 public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot) {
2 synchronized (mConstructorArgs) {
3 final AttributeSet attrs = Xml.asAttributeSet(parser);
4 mConstructorArgs[0] = mContext;
5 View result = root;
6
7 try {
8 //……
9 final String name = parser.getName();
10 //……
11 if (TAG_MERGE.equals(name)) {
12 //……
13 } else {
14 // Temp is the root view that was found in the xml
15 View temp = createViewFromTag(name, attrs);
16
17 ViewGroup.LayoutParams params = null;
18
19 if (root != null) {
20 if (DEBUG) {
21 System.out.println("Creating params from root: " +
22 root);
23 }
24 // Create layout params that match root, if supplied
25 params = root.generateLayoutParams(attrs);
26 if (!attachToRoot) {
27 // Set the layout params for temp if we are not
28 // attaching. (If we are, we use addView, below)
29 temp.setLayoutParams(params);
30 }
31 }
32
33 if (DEBUG) {
34 System.out.println("-----> start inflating children");
35 }
36 // Inflate all children under temp
37 rInflate(parser, temp, attrs);
38 if (DEBUG) {
39 System.out.println("-----> done inflating children");
40 }
41
42 // We are supposed to attach all the views we found (int temp)
43 // to root. Do that now.
44 if (root != null && attachToRoot) {
45 root.addView(temp, params);
46 }
47
48 // Decide whether to return the root that was passed in or the
49 // top view found in xml.
50 if (root == null || !attachToRoot) {
51 result = temp;
52 }
53 }
54
55 } catch (XmlPullParserException e) {
56 //……
57 }
58
59 return result;
60 }
61 }
这个inflate()方法会使用XmlPullParser对象来对xml文件进行解析,先不管merge相关的布局,其解析的过程:
记录xml的所有属性attrs = Xml.asAttributeSet(parser);
解析最外层的layout View的name,这个name标志了这个View的类型,例如LinearLayout、RelativeLayout、以及其他类型的View等;
调用temp = createViewFromTag(name, attrs),创建一个对应类型的View。
如果temp 存在parent,也就是root,则调用params = root.generateLayoutParams(attrs)来创建相应的依附于root的参数对象,创建这个参数对象的时候,也同事把相应的attrs属性传进去,这些属性就是从xml文件里面解析出来的。
然后调用rInflate(parser, temp, attrs)方法,继续往下递归地解析;
最后,如果temp存在parent,也就是root,再调用root.addView(temp, params)把这个View添加进root。
我们再看看rInflate(parser, temp, attrs)方法的实现,这个实现里面是使用递归的方法进行解析的,看看代码:
1 private void rInflate(XmlPullParser parser, View parent, final AttributeSet attrs)
2 throws XmlPullParserException, IOException {
3
4 final int depth = parser.getDepth();
5 int type;
6
7 while (((type = parser.next()) != XmlPullParser.END_TAG ||
8 parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
9
10 if (type != XmlPullParser.START_TAG) {
11 continue;
12 }
13
14 final String name = parser.getName();
15
16 if (TAG_REQUEST_FOCUS.equals(name)) {
17 parseRequestFocus(parser, parent);
18 } else if (TAG_INCLUDE.equals(name)) {
19 //……
20 } else if (TAG_MERGE.equals(name)) {
21 throw new InflateException("<merge /> must be the root element");
22 } else {
23 final View view = createViewFromTag(name, attrs);
24 final ViewGroup viewGroup = (ViewGroup) parent;
25 final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
26 rInflate(parser, view, attrs);
27 viewGroup.addView(view, params);
28 }
29 }
30
31 parent.onFinishInflate();
32 }
rInflate(parser, temp, attrs)方法的实现和inflate()方法的实现是相似的,其过程也需要:
- parser.getName();
- createViewFromTag(name, attrs);
- generateLayoutParams(attrs);
- rInflate(parser, view, attrs);
- addView(view, params)。
其中比较重要的是rInflate(parser, temp, attrs)方法里面继续调用自己进行递归。
另外就是Activity类的setContentView(int layoutResID)方法:
1 public void setContentView(int layoutResID) {
2 getWindow().setContentView(layoutResID);
3 }
这里有一个疑问,getWindow()返回的是什么东东呢?getWindow()返回一个Window类型的对象,但是Window是一个抽象类,并不能直接实例化一个对象。那么getWindow()返回的肯定是Window类的子类对象。那么这个子类是什么呢?为了得到这个类对象到底是什么,我们可以做一个实验,那就是写一个空内容的xml文件取名为test.xml,如下:
1 <?xml version="1.0" encoding="utf-8"?>
2 <LinearLayout > </LinearLayout>
然后在一个Activity的onCreate()方法里面调用setContentView(R.layout.test)方法来加载这个layout的xml文件。
程序会运行错误,有如下错误:
从上面的错误可以看出来,原来Activity的setContentView(int layoutResID)方法是调用了PhoneWindow类的setContentView(int layoutResID)方法,那么现在我们打开PhoneWindow类文件进行研究。如果在eclipse上面打不开这个PhoneWindow类文件,那么就需要在SDK的源代码的目录下去寻找PhoneWindow.java文件来阅读。这个SDK的源代码的目录是自己人为的添加进去的,如果不清楚这一点,那么就需要看看有“阅读SDK源代码”内容的那一章节。
打开PhoneWindow类文件后,点击ctrl+F寻找setContentView(int layoutResID)方法,代码如下:
1 public void setContentView(int layoutResID) {
2 if (mContentParent == null) {
3 installDecor();
4 } else {
5 mContentParent.removeAllViews();
6 }
7 mLayoutInflater.inflate(layoutResID, mContentParent);
8 final Callback cb = getCallback();
9 if (cb != null) {
10 cb.onContentChanged();
11 }
12 }
原来这个setContentView()方法,最终还是调用了LayoutInflater类的inflate(int resource, ViewGroup root)方法。其中mContentParent参数是这个PhoneWindow里面本来就存在的一个FrameLayout。如果mContentParent为空的话,就会先初始化一个FrameLayout赋值给mContentParent。而mContentParent还有parent,这个parent就是一个DecorView,叫做装饰View。Android系统的要显示一个屏幕的内容,这些内容是有一定的层次关系的,详细可以查看下面的文章:
上面使用了HierarchyView工具来查看PhoneWindow里面的布局结构。