android解析xml简单分析

一直想写关于android解析xml文件构建View视图的学习笔记,到今天 才算静下心来大致过了一遍它的源码,特此简单的分析一下它的大致脉络。具体怎么解析xml的是由XmlPullParser这个类来完成的,至于解析的细节就不作说明。算是个简单的学习笔记。

在android里面是由LayoutInflater这个类来完成xml构建View的工作,主要工作交给该类的如下几个重载方法来完成:

前面上那个inflate方法最终会调用租后一个inflate方法来完成解析的工作,让我们看看这个方法都做了些什么:

public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot) {
        synchronized (mConstructorArgs) {
		    //获取AttributeSet对象
            final AttributeSet attrs = Xml.asAttributeSet(parser);
            Context lastContext = (Context)mConstructorArgs[0];
             //保存mContext对象,在最终用反射生成View对象的时候会用到该参数
             mConstructorArgs[0] = mContext;
	  //最终解析获取产生的返回的View对象
            View result = root;

                // 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!");
                }
               //节点名,即API中的控件或者自定义View完整限定名
                final String name = parser.getName();
             
		 //处理标签
                if (TAG_MERGE.equals(name)) {
                    if (root == null || !attachToRoot) {
                        throw new InflateException(" can be used only with a valid "
                                + "ViewGroup root and attachToRoot=true");
                    }

                    rInflate(parser, root, attrs, false);
                } else {
                    // Temp is the root view that was found in the xml
                   //Temp是xml文件里面的根View
                    View temp;
                    if (TAG_1995.equals(name)) {//处理标签
                        temp = new BlinkLayout(mContext, attrs);
                    } else {//
		       //创建该xml布局文件所对应的根View。 
                        temp = createViewFromTag(root, name, attrs);
                    }
                     
		    //root 不为null的情况下就创建LayoutParams参数
                    ViewGroup.LayoutParams params = null;                              
                    if (root != null) {
                       
                        // Create layout params that match root, if supplied
                       params = root.generateLayoutParams(attrs);
		        //如果xml的根View不跟root绑定	
                        if (!attachToRoot) {
                            // Set the layout params for temp if we are not
                            // attaching. (If we are, we use addView, below)
                            //设置xml根View的params
                             temp.setLayoutParams(params);
                        }
                    }
                  
                    // Inflate all children under temp
		   //即系xml文件根View里面的 的所有子节点,也就是添加temp View的所有的子View
                    rInflate(parser, temp, attrs, true);
                    
                    // We are supposed to attach all the views we found (int temp)
                    // to root. Do that now.
		  //如果root!=null并且绑定,就返回root
                    if (root != null && attachToRoot) {
                         //将params设置给temp,
                         //并将temp添加到root里面
                         root.addView(temp, params);
                    }

                    // Decide whether to return the root that was passed in or the
                    // top view found in xml.
		//如果root为null或者不绑定root就返回temp
                    if (root == null || !attachToRoot) {
                        result = temp;
                    }
                }

            return result;
        }
    }

从上面的代码我们可以简单的总结一下得到如下结论:

1)如果root为null的情况下,xml文件对应的根View(也就是上面对应的temp)的LayoutParams是为null的,其实这正是我们在ListView直接调用:

  convertView = App.getLayoutInflater().inflate(R.layout.item, null);  
使得item.xml的根View的宽和高设置无效的原因,解决方法很简,详细解说 点击此处。

2)LayoutParam是由AttributeSet属性传递个generateLayoutParams方法来构建具体的LayoutParams对象,该AttributeSet对象在inflate的第一行产生。

   在ViewGroup 类里面generateLayoutParams方法如下:

 

 public LayoutParams generateLayoutParams(AttributeSet attrs) {
        return new LayoutParams(getContext(), attrs);
    }

public LayoutParams(Context c, AttributeSet attrs) {
            TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.ViewGroup_Layout);
            setBaseAttributes(a,
                    R.styleable.ViewGroup_Layout_layout_width,
                    R.styleable.ViewGroup_Layout_layout_height);
            a.recycle();
  }
//设置基本的layout_width和layout_height属性
 protected void setBaseAttributes(TypedArray a, int widthAttr, int heightAttr) {
            width = a.getLayoutDimension(widthAttr, "layout_width");
            height = a.getLayoutDimension(heightAttr, "layout_height");
        }
//可以看出,如果不设置layout_width或者layout_height的话,会抛出异常。
public int getLayoutDimension(int index, String name) {
        index *= AssetManager.STYLE_NUM_ENTRIES;
        final int[] data = mData;
        final int type = data[index+AssetManager.STYLE_TYPE];
        if (type >= TypedValue.TYPE_FIRST_INT
                && type <= TypedValue.TYPE_LAST_INT) {
            return data[index+AssetManager.STYLE_DATA];
        } else if (type == TypedValue.TYPE_DIMENSION) {
            return TypedValue.complexToDimensionPixelSize(
                data[index+AssetManager.STYLE_DATA], mResources.mMetrics);
        }
      
        throw new RuntimeException(getPositionDescription()
                + ": You must supply a " + name + " attribute.");
    }

也就是说在创建xml跟对象的时候会设置其LayoutParams对象并初始化其width和height属性,这两个属性在对View进行Measure的时候用到,这点需要注意,在ViewGroup中提供Measurechild方法:在该方法中会调用View的getLayoutParams方法来获取设置的LayoutParams对象,并取的其lp.width和lp.height来进行child的Measure操作。
 protected void measureChild(View child, int parentWidthMeasureSpec,
            int parentHeightMeasureSpec) {
        final LayoutParams lp = child.getLayoutParams();
        //需要获取lp.width
        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
                mPaddingLeft + mPaddingRight, lp.width);
        //需要获取lp.height  
        final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
                mPaddingTop + mPaddingBottom, lp.height);

        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }

其实通过上面的代码还可以得出一个关于View Measure的结论:childView的measureSpec是是由parentView的measureSpec和childView自身的LayoutParams来共同决定的

3)如果root不为null,那么就调用root.root.generateLayoutParams(attrs);方法产生一个LayoutParams对象,来设置xml根View的layoutParams.具体有两种方法

   3.1)attachToRoot为false的情况下直接temp.setLayoutParams(params)

   3.2)attachToRoot为true的情况下调用root.addView(temp.params)来讲params对象设置给temp.而ViewGroup的addView方法最终会调用如下方法:

    

 private void addViewInner(View child, int index, LayoutParams params,
            boolean preventRequestLayout) {

        if (!checkLayoutParams(params)) {
            params = generateLayoutParams(params);
        }
        //将params设置给child也就是上面所说的temp
        if (preventRequestLayout) {
            child.mLayoutParams = params;
        } else {
            child.setLayoutParams(params);
        }

    }

4)通过createViewFromTag来创建xml布局文件对应的根View,下面看看这个方法都做了些什么:

//此时第一次调用该方法,parent为上面所说的temp也就是xml的根View
View createViewFromTag(View parent, String name, AttributeSet attrs) {
        if (name.equals("view")) {
            name = attrs.getAttributeValue(null, "class");
        }

		View view;
		//此处省略的工厂方法创建创建view的实例的代码,因为在平时的使用中并没有用到这些工厂,所以忽略此代码
		
		if (view == null) {
		       //android 自带view的实例
			if (-1 == name.indexOf('.')) {
			       //parent参数才此处没有用到
				view = onCreateView(parent, name, attrs);
			} else {//创建自定义view的实例
                            view = createView(name, null, attrs);//注意第二个参数为null
			}
		}
		return view;
    }

在通过xml文件的Tag创建View对象的时候,android对自定View空间也再次做了处理,这就是我们可以在xml文件中使用自定义View的原因。在处理android自带的View的时候调用了onCreateView方法该方法内部仅仅是调用了onCreateView方法。而onCreanteView又是直接调用createView方法,所以我们来看看createView即可:

public final View createView(String name, String prefix, AttributeSet attrs)
            throws ClassNotFoundException, InflateException {
        Constructor constructor = sConstructorMap.get(name);
        Class clazz = null;
		if (constructor == null) {//缓存中没有name对应View的constructor对象
			// Class not found in the cache, see if it's real, and try to add it
			//获取name标签所代表view类的class对象
			clazz = mContext.getClassLoader().loadClass(
					prefix != null ? (prefix + name) : name).asSubclass(View.class);
			。。。。。
			constructor = clazz.getConstructor(mConstructorSignature);
			//放入缓存中去
			sConstructorMap.put(name, constructor);
		} else {
			// If we have a filter, apply it to cached constructor
			if (mFilter != null) {
				
			}
		}
        //传递参数,生成View实例对象
		Object[] args = mConstructorArgs;
		args[1] = attrs;
		return constructor.newInstance(args);

      
    }

上面的方法也很简单,总的来说是用java的反射机制来创建一个View多想。但是需要略作说明:

1)该方法根据第二个参数prefix来判断是否是自定义组件,如果null的话说明是程序员自定义的view组件,否则就是android api里面的view组件

2)为了提高解析xml创建View的效率,在LayoutInflate里面对tag及该tag对应View的Constructor做了缓存处理。这样在同一个xml文件里面有相同的Tag的时候就不用再次用ClassLoader加载clss文件了。

3)最后 三行代码完成了构建View的工作,其中args[0]对应的是mContext对象,而args[1]就AttributeSet这个对象了。最总通过构造器Constructor.newInstance来创建完成View的创建。这也是为什么View或者ViewGroup没有提供默认构造器的原因:防止自定义View的时候不重写View或者ViewGroup的构造器,这样解析的时候会出错。

到此位置xml的根标签解析成View对象的工作已经完成,那么xml文件中根View包含的子View又是怎么解析的呢?其实想想也应该知道,肯定也是根据子View的tag调用createViewFromTag来创建具体的子View,因为是一个View Tree也就是简单的递归调用而已。事实上查看inflate方法可知道在对xml子view的解析是在xml根View创建完成后调用rInlfate方法来完成的

 //即系xml文件根View里面的 的所有子节点,也就是添加temp View的所有的子View
                    rInflate(parser, temp, attrs, true);
最后就让我们看看rInflate方法都做了些什么吧:

/
    void rInflate(XmlPullParser parser, View parent, final 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_INCLUDE.equals(name)) {//解析标签  
                if (parser.getDepth() == 0) {
                    throw new InflateException(" cannot be the root element");
                }
				//解析标签  
                parseInclude(parser, parent, attrs);
            } else if (TAG_MERGE.equals(name)) {//处理标签  
                throw new InflateException(" must be the root element");
            } else if (TAG_1995.equals(name)) {//处理标签
                final View view = new BlinkLayout(mContext, attrs);
                final ViewGroup viewGroup = (ViewGroup) parent;
                final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
                rInflate(parser, view, attrs, true);
                viewGroup.addView(view, params);                
            } else {
			    //根据当前节点名构建一个View实例对象  
                final View view = createViewFromTag(parent, name, attrs);
                final ViewGroup viewGroup = (ViewGroup) parent;
                //创建一个layoutPrarams对象
                 final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
	    //继续递归调用  解析当前view 的子view
                rInflate(parser, view, attrs, true);
	     //把生成的view添加到parent view中
	   //需要注意的是addView每次都调用了该子view的parentView所产生的Layoutparams对象
                viewGroup.addView(view, params);
            }
        }//end while

        if (finishInflate) parent.onFinishInflate();
    }


由此,android通过LayoutInflater解析xml创建View对象的简单脉络分析完毕,很清晰,通过追踪它的源码很容易就把此脉络理清;至于更深层次的解析原理,水平有限就不做分析了,如有不当之处欢迎批评指正







你可能感兴趣的