加载xml布局文件原理

| 分类 android  sdk源码  | 标签 Android  LayoutInflater 

我们以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文件。

程序会运行错误,有如下错误: img

从上面的错误可以看出来,原来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里面的布局结构。


上一篇     下一篇