一直想写关于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的根ViewView 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<? extends View> constructor = sConstructorMap.get(name);        Class<? extends View> 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 constructorif (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对象的简单脉络分析完毕,很清晰,通过追踪它的源码很容易就把此脉络理清;至于更深层次的解析原理,水平有限就不做分析了,如有不当之处欢迎批评指正







更多相关文章

  1. 浅析Android下的Android.mk文件
  2. 在Android下创建文件夹
  3. Android中Math取整的三个方法
  4. Android Launcher开发(一)LiveFolder(实时文件夹) 完全解析
  5. android工程中的R.java文件
  6. Android Studio启动时卡在Fetching Android SDK 以及导入Eclipse
  7. Android文件选择器

随机推荐

  1. android用jdbc多线程操作sqlite小结
  2. C语言的一些练习以及自己写一个猜数字小
  3. 算法面试专题课(Java版)
  4. centos LVM(逻辑卷管理)
  5. Android(安卓)NDK开发之旅(5):Android(安
  6. Unity3D之坐标系的转换
  7. 数据结构之哈希表
  8. 基于业务和平台理解数字营销概念
  9. 打卡学习
  10. Plotly中4种文本类型设置详解