你知道AttributeSet与XmlPullParser的关系吗?

前言

AttributeSet与XmlPullParser这2个接口在解析xml文件时经常配合使用,二者可进行相互转换,理清二者的关系对于深入理解LayoutIflater.inflate()源码时,有着重要作用。
故本文的目的在于理清二者之间的关系,简述XmlPullParser的基本用法,为后续理解LayoutIflater.inflate方法源码打下基础。

类图结构

AttributeSet与XmlPullParser的类图关系如下图所示:


你知道AttributeSet与XmlPullParser的关系吗?_第1张图片
类图结构

AttributeSet接口抽象了访问xml节点属性的方法,XmlPullPaser接口抽象了解析xml文件的方法,这个2个接口具有部分相同的方法声明,如下图所示。这些方法是XmlPull解析常用来访问xml节点属性的方法。

    int getAttributeCount();
    String getAttributeName(int index);
    String getAttributeValue(int index);
    String getAttributeValue(String namespace, String name);
    String getPositionDescription();

AttributeSet与XmlPullParser的关系

在Android中AttributeSet接口的实现类有XmlPullAttribute和Parser(XmlResourceParser接口的实现类,该接口继承了XmlPullParser和AttributeSet接口)。
我们先来看看XmlPullAttribute的实现,部分代码如下:

class XmlPullAttributes implements AttributeSet {
    public XmlPullAttributes(XmlPullParser parser) {
        mParser = parser;
    }

    public int getAttributeCount() {
        return mParser.getAttributeCount();
    }

    public String getAttributeName(int index) {
        return mParser.getAttributeName(index);
    }

    public String getAttributeValue(int index) {
        return mParser.getAttributeValue(index);
    }

    public String getAttributeValue(String namespace, String name) {
        return mParser.getAttributeValue(namespace, name);
    }

    public String getPositionDescription() {
        return mParser.getPositionDescription();
    }

    public int getAttributeNameResource(int index) {
        return 0;
    }

    public int getAttributeListValue(String namespace, String attribute,
            String[] options, int defaultValue) {
        return XmlUtils.convertValueToList(
            getAttributeValue(namespace, attribute), options, defaultValue);
    }
     ............
    //AttributeSet接口的剩余方法实现省略,其实现与getAttributeListValue()方法
    //相似,都依赖XmlUtils与getAttributeValue()。
}

可见该类关联了一个XmlPullParser类型的成员变量(实际上是KXmlParser),AttributeSet接口与XmlPullParser接口相同声明函数的实现都是调用的mParser的同名函数;而AttributeSet接口的其他方法皆依赖于getAttributeValue方法来实现,即也依赖于mParser。
从代码实现来看,很自然的会联想到装饰者设计模式,虽然从类图结构上来看,并非属于严格意义上的装饰者模式,但我想可以将AttributeSet理解为XmlPullParser的装饰者,只不过这个装饰者没有全部装饰而已,只是装饰了XmlParser用于访问xml节点属性的那一部分。
自创部分装饰设计,强行解说2333....
综上所述:AttributeSet是对XmlPullParser的部分装饰,装饰了用于访问xml节点属性的部分。或者理解为AttributeSet是对XmlPullParser中用于访问xml节点属性方法的封装。

至于为什么要这么做呢?拙见如下:

  1. AttributeSet这个接口更适合Android的设计,其语意性更强;如果将View的构造函数中的AttributeSet换为XmlPullParser,看起来是不是有一种别扭的感觉的,而AttributeSet(属性集合)这个命名更易让人理解接受,装饰的作用之一就是可以“改头换面”嘛。
  2. 单一职责原则,XmlPullParser接口的职责不单单是用于访问xml节点的属性,从中分离出一个用于访问节点属性的接口岂不美哉?
  3. 对XmlPullParser中的Xml节点属性访问方法进行扩展,方便使用,如getAttributeIntValue方法可直接获取int类型的属性值,若使用XmlPullParser则只能获取String类型的数据,还需要我们自己转换一次。

下面来看看XmlResourceParser接口。
该接口同时继承了XmlPullParser接口以及AttributeSet接口,作用有二。

  1. 对XmlPullParser起到部分装饰的作用,实现XmlResourceParser接口,即可达到目的。
  2. 继承XmlPullParser接口,实现一个专用于Android Xml资源文件的解析器,如解析layout、drawable等目录下的xml文件。
    Android中的常见打开方式如下:
        Resources resources = getResources();
        XmlResourceParser  parser0 = resources.getXml(R.xml.test);
        XmlResourceParser  parser1 = resources.getLayout(R.layout.activity_main);
        XmlResourceParser  parser2 = resources.getAnimation(R.anim.test);

Drawable目录下的xml解析并未提供我们可直接获取XmlResourceParser的API,其解析封装在ResourseImpl类(@hide)的方法中。

    /**
        * Loads a drawable from XML or resources stream.
        */
    private Drawable loadDrawableForCookie(Resources wrapper, TypedValue value, int id,Resources.Theme theme) {
        ............
        if (file.endsWith(".xml")) {
                final XmlResourceParser rp = loadXmlResourceParser(
                        file, id, value.assetCookie, "drawable");
                dr = Drawable.createFromXml(wrapper, rp, theme);
                rp.close();
            } else {
                final InputStream is = mAssets.openNonAsset(
                        value.assetCookie, file, AssetManager.ACCESS_STREAMING);
                dr = Drawable.createFromResourceStream(wrapper, value, is, file, null);
                is.close();
            }
       ............
}

XmlResourceParser的实现类为Parser类,该类属于BlockXml类的一个内部类,用于解析Android中的layout布局文件。
这里说一个小技巧,方便想要自行查看源码的朋友:
在Android Studio中可通过双击Shift键来快速查找类文件,可以查到一些隐藏类文件的源码。
总结
1、AttributeSet接口是对XmlPullParser接口的部分装饰,装饰了XmlPullParser中xml节点属性的访问方法。AttributeSet接口语意性更强,更适合Android架构的整体设计。
2、其实现类无论是Parser还是XmlPullAttribute,它只是起到一个装饰作用,最终对xml节点属性的访问都是通过里面的XmlPullParser的节点属性访问方法来完成的。

至此,AttributeSet与XmlPullParser的关系已经分析清楚,后文只是方便自己后续查看,主要记录XmlPullParser的基本使用。

转换分析

XmlPullParser转换为AttributeSet的方法为:

  Xml.asAttributeSet(parser0);

进去看看源码:

//如果传入的XmlPullParser类型为XmlResourceParser(Parser),返回的就是XmlResourceParser(Parser)
//反之则new 一个XmlPullAttributeSet
 public static AttributeSet asAttributeSet(XmlPullParser parser) {
        return (parser instanceof AttributeSet)
                ? (AttributeSet) parser
                : new XmlPullAttributes(parser);
    }

XmlPullParser的基本使用

由上文可知,Android中XmlPull解析器有2种类型,一种是专用于Android xml资源文件的解析器,一种是org.xmlpull.v1自带的xml解析器,无论是那种类型,对外而言,其用法都一样,这就是面向接口编程的一个好处。
实际应用当中,使用较多的应该是org.xmlpull.v1自带的xml解析器,毕竟Android的专用资源解析器,它已经封装好自动解析了。

         public static void main (String args[])
                throws XmlPullParserException, IOException
        {
            XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
            //区分namespace
            factory.setNamespaceAware(true);
            XmlPullParser xpp = factory.newPullParser();
   
           /**
             * 解析最开始并非位于START_DOCUMENT,多次调用next,会依次位于
             * START_DOCUMENT
             * START_TAG(XML节点属性解析只能在该事件类型中进行解析)
             * TEXT(无Text部分“Hello World”则跳过该事件类型)
             * END_TAG
             * End document
             */
            xpp.setInput( new StringReader ( "Hello World!" ) );
            int eventType = xpp.getEventType();
            while (eventType != XmlPullParser.END_DOCUMENT) {
                if(eventType == XmlPullParser.START_DOCUMENT) {
                    System.out.println("Start document");
                } else if(eventType == XmlPullParser.START_TAG) {
                    System.out.println("Start tag "+xpp.getName());
                } else if(eventType == XmlPullParser.END_TAG) {
                    System.out.println("End tag "+xpp.getName());
                } else if(eventType == XmlPullParser.TEXT) {
                    System.out.println("Text "+xpp.getText());
                }
                eventType = xpp.next();
            }
            System.out.println("End document");
        }

结果:

 Start document
 Start tag foo
 Text Hello World!
 End tag foo
 End document
 

你可能感兴趣的:(你知道AttributeSet与XmlPullParser的关系吗?)