Android系列--DOM、SAX、Pull解析XML

您能够经过点击 右下角 的按钮 来对文章内容做出评价, 也能够经过左下方的 关注按钮 来关注个人博客的最新动态。 

若是文章内容对您有帮助, 不要忘记点击右下角的 推荐按钮 来支持一下哦   

若是您对文章内容有任何疑问, 能够经过评论或发邮件的方式联系我: 501395377@qq.com  / lzp501395377@gmail.com

若是须要转载,请注明出处,谢谢!!

本篇随笔将详细讲解如何在Android当中解析服务器端传过来的XML数据,这里将会介绍解析xml数据格式的三种方式,分别是DOM、SAX以及PULL。node

1、DOM解析XMLandroid

咱们首先来看看DOM(Document Object Model)这种方式解析xml,经过DOM解析xml在j2ee开发中很是的常见,它将整个xml当作是一个树状的结构,在解析的时候,会将整个xml文件加载到咱们的内存当中,而后经过DOM提供的API来对咱们的xml数据进行解析,这种方式解析xml很是的方便,而且咱们能够经过某个节点访问到其兄弟或者是父类、子类节点。那么经过DOM来解析xml的步骤是怎样的呢?编程

1.首先经过DocumentBuilderFactory这个类来构建一个解析工厂类,经过newInstance()的方法能够获得一个DocumentBuilderFactory的对象。数组

2.经过上面的这个工厂类建立一个DocumentBuilder的对象,这个类就是用来对咱们的xml文档进行解析,经过DocumentBuilderFactory的newDocumentBuilder()方法服务器

3.经过建立好的 DocumentBuilder 对象的 parse(InputStream) 方法就能够解析咱们的xml文档,而后返回的是一个Document的对象,这个Document对象表明的就是咱们的整个xml文档。dom

4.获得了整个xml的Document对象后,咱们能够得到其下面的各个元素节点(Element),一样每一个元素节点可能又有多个属性(Attribute),根据每一个元素节点咱们又能够遍历该元素节点下面的子节点等等。ide

在这里要说明一下,在DOM的API当中,Node这个接口表明了咱们整个的DOM对象的最初数据类型,它表明了整个document树中的每个单一节点。全部实现了Node这个接口的对象均可以处理其孩子节点,固然,并非每一个节点都有children,例如TextNode(文本节点),经过Node的 nodeName、nodeValue、attributes这三个属性,咱们能够很方便的获得每一个Node节点的节点名字、节点的值、节点属性等,下面咱们来看看不一样类型的Node节点其nodeName、nodeValue、attributes三个属性分别表明的是什么:工具

Interface nodeName nodeValue attributes
Attr same as Attr.name same as Attr.value null
CDATASection "#cdata-section" same as CharacterData.data, the content of the CDATA Section null
Comment "#comment" same as CharacterData.data, the content of the comment null
Document "#document" null null
DocumentFragment "#document-fragment" null null
DocumentType same as DocumentType.name null null
Element same as Element.tagName null NamedNodeMap
Entity entity name null null
EntityReference name of entity referenced null null
Notation notation name null null
ProcessingInstruction same as ProcessingInstruction.target same as ProcessingInstruction.data null
Text "#text" same as CharacterData.data, the content of the text node null

其实咱们用的最多的就是Element和Text,经过Element的nodeName属性能够获得这个节点的标签名,Text对象的nodeValue获得的就是元素节点的文本值内容,下面咱们来看看一个经过DOM解析xml的一个代码案例:布局

首先咱们构建一个xml的文档,这个文档等下会放在咱们的服务器上,经过http协议来获得这个xml文档,而后在咱们的Android客户端对其进行解析ui

<?xml version="1.0" encoding="UTF-8"?>
<persons>
    <person id="1">
        <name>小罗</name>
        <age>21</age>
    </person>
    <person id="2">
        <name>android</name>
        <age>15</age>
    </person>
</persons>

下面咱们来看看DOM解析服务器端xml的工具类:

public class DomParserUtils
{
    public static List<Person> parserXmlByDom(InputStream inputStream) throws Exception
    {
        List<Person> persons = new ArrayList<Person>();
        //    获得一个DocumentBuilderFactory解析工厂类
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        //    获得一个DocumentBuilder解析类
        DocumentBuilder builder = factory.newDocumentBuilder();
        //    接收一个xml的字符串来解析xml,Document表明整个xml文档
        Document document = builder.parse(inputStream);
        //    获得xml文档的根元素节点
        Element personsElement = document.getDocumentElement();
        //    获得标签为person的Node对象的集合NodeList
        NodeList nodeList = personsElement.getElementsByTagName("person");
        for(int i = 0; i < nodeList.getLength(); i++)
        {
            Person person = new Person();
            //    若是该Node是一个Element
            if(nodeList.item(i).getNodeType() == Document.ELEMENT_NODE)
            {
                Element personElement = (Element)nodeList.item(i);
                //    获得id的属性值
                String id = personElement.getAttribute("id");
                person.setId(Integer.parseInt(id));
                
                //    获得person元素下的子元素
                NodeList childNodesList = personElement.getChildNodes();
                for(int j = 0; j < childNodesList.getLength(); j++)
                {
                    if(childNodesList.item(j).getNodeType() == Document.ELEMENT_NODE)
                    {
                        //    解析到了person下面的name标签
                        if("name".equals(childNodesList.item(j).getNodeName()))
                        {
                            //    获得name标签的文本值
                            String name = childNodesList.item(j).getFirstChild().getNodeValue();
                            person.setName(name);
                        }
                        else if("address".equals(childNodesList.item(j).getNodeName()))
                        {
                            String age = childNodesList.item(j).getFirstChild().getNodeValue();
                            person.setAge(Integer.parseInt(age));
                        }
                    }
                }
                
                persons.add(person);
                person = null;
            }
        }
        return persons;
    }
}

经过DOM解析xml的好处就是,咱们能够随时访问到某个节点的相邻节点,而且对xml文档的插入也很是的方便,很差的地方就是,其会将整个xml文档加载到内存中,这样会大大的占用咱们的内存资源,对于手机来讲,内存资源是很是很是宝贵的,因此在手机当中,经过DOM这种方式来解析xml是用的比较少的。

2、SAX解析XML

SAX(Simple API for XML),接着咱们来看看另外一种解析xml的方式,经过sax来对xml文档进行解析。

SAX是一个解析速度快而且占用内存少的xml解析器,很是适合用于Android等移动设备。 SAX解析XML文件采用的是事件驱动,也就是说,它并不须要解析完整个文档,在按内容顺序解析文档的过程当中,SAX会判断当前读到的字符是否合法XML语法中的某部分,若是符合就会触发事件。所谓事件,其实就是一些回调(callback)方法,这些方法(事件)定义在ContentHandler接口。下面是一些ContentHandler接口经常使用的方法:

startDocument()
当遇到文档的开头的时候,调用这个方法,能够在其中作一些预处理的工做。

endDocument()
和上面的方法相对应,当文档结束的时候,调用这个方法,能够在其中作一些善后的工做。

startElement(String namespaceURI, String localName, String qName, Attributes atts)
当读到一个开始标签的时候,会触发这个方法。namespaceURI就是命名空间,localName是不带命名空间前缀的标签名,qName是带命名空间前缀的标签名。经过atts能够获得全部的属性名和相应的值。要注意的是SAX中一个重要的特色就是它的流式处理,当遇到一个标签的时候,它并不会纪录下之前所碰到的标签,也就是说,在startElement()方法中,全部你所知道的信息,就是标签的名字和属性,至于标签的嵌套结构,上层标签的名字,是否有子元属等等其它与结构相关的信息,都是不得而知的,都须要你的程序来完成。这使得SAX在编程处理上没有DOM来得那么方便。

endElement(String uri, String localName, String name)
这个方法和上面的方法相对应,在遇到结束标签的时候,调用这个方法。

characters(char[] ch, int start, int length)
这个方法用来处理在XML文件中读到的内容,第一个参数用于存放文件的内容,后面两个参数是读到的字符串在这个数组中的起始位置和长度,使用new String(ch,start,length)就能够获取内容。

上面提到了重要的一点,sax解析xml是基于事件流的处理方式的,所以每解析到一个标签,它并不会记录这个标签以前的信息,而咱们只会知道当前这个表情的名字和它的属性,至于标签里面的嵌套,上层标签的名字这些都是没法知道的。

sax解析xml最重要的步骤就是定义一个咱们本身的Handler处理类,咱们可让其继承 DefaultHandler 这个类,而后在里面重写其回调方法,在这些回调方法里来作咱们的xml解析

下面咱们就经过一个实例来看看若是经过SAX来解析xml,首先定义一个咱们本身的Handler类:

public class MyHandler extends DefaultHandler
{
    private List<Person> persons;
    private Person person;
    //    存放当前解析到的标签名字
    private String currentTag;
    //    存放当前解析到的标签的文本值
    private String currentValue;
    
    public List<Person> getPersons()
    {
        return persons;
    }
    
    //    当解析到文档开始时的回调方法
    @Override
    public void startDocument() throws SAXException
    {
        persons = new ArrayList<Person>();
    }
    
    //    当解析到xml的标签时的回调方法
    @Override
    public void startElement(String uri, String localName, String qName,
            Attributes attributes) throws SAXException
    {
        if("person".equals(qName))
        {
            person = new Person();
            //    获得当前元素的属性值
            for(int i = 0; i < attributes.getLength(); i++)
            {
                if("id".equals(attributes.getQName(i)))
                {
                    person.setId(Integer.parseInt(attributes.getValue(i)));
                }
            }
        }
        //    设置当前的标签名
        currentTag = qName;
    }
    
    //    当解析到xml的文本内容时的回调方法
    @Override
    public void characters(char[] ch, int start, int length)
            throws SAXException
    {
        //    获得当前的文本内容
        currentValue = new String(ch,start, length);
        //    当currentValue不为null、""以及换行时
        if(currentValue != null && !"".equals(currentValue) && !"\n".equals(currentValue))
        {
            //    判断当前的currentTag是哪一个标签
            if("name".equals(currentTag))
            {
                person.setName(currentValue);
            }
            else if("age".equals(currentTag))
            {
                person.setAge(Integer.parseInt(currentValue));
            }
        }
        //    清空currentTag和currentValue
        currentTag = null;
        currentValue = null;
    }
    
    //    当解析到标签的结束时的回调方法
    @Override
    public void endElement(String uri, String localName, String qName)
            throws SAXException
    {
        if("person".equals(qName))
        {
            persons.add(person);
            person = null;
        }
    }
}

接着看看SAX解析xml的Util类:

public class SaxParserUtils
{
    public static List<Person> parserXmlBySax(InputStream inputStream) throws Exception
    {
        //    建立一个SAXParserFactory解析工厂类
        SAXParserFactory factory = SAXParserFactory.newInstance();
        //    实例化一个SAXParser解析类
        SAXParser parser = factory.newSAXParser();
        //    实例化咱们的MyHandler类
        MyHandler myHandler = new MyHandler();
        //    根据咱们自定义的Handler来解析xml文档
        parser.parse(inputStream, myHandler);

        return myHandler.getPersons();
    }
}

3、PULL解析XML

最后来介绍第三种解析xml的方式,pull。pull解析和sax解析相似,都是基于事件流的方式,在Android中自带了pull解析的jar包,因此咱们不须要导入第三方的jar包了。

Pull解析器和SAX解析器的区别

Pull解析器和SAX解析器虽有区别但也有类似性。他们的区别为:SAX解析器的工做方式是自动将事件推入注册的事件处理器进行处理,所以你不能控制事件的处理主动结束;

而Pull解析器的工做方式为容许你的应用程序代码主动从解析器中获取事件,正由于是主动获取事件,所以能够在知足了须要的条件后再也不获取事件,结束解析。这是他们主要的区别。

而他们的类似性在运行方式上,Pull解析器也提供了相似SAX的事件(开始文档START_DOCUMENT和结束文档END_DOCUMENT,开始元素START_TAG和结束元素END_TAG,遇到元素内容TEXT等),但须要调用next() 方法提取它们(主动提取事件)。

Android系统中和Pull方式相关的包为org.xmlpull.v1,在这个包中提供了Pull解析器的工厂类XmlPullParserFactory和Pull解析器XmlPullParser,XmlPullParserFactory实例调用newPullParser方法建立XmlPullParser解析器实例,接着XmlPullParser实例就能够调用getEventType()和next()等方法依次主动提取事件,并根据提取的事件类型进行相应的逻辑处理。

下面咱们就来经过一个代码来看看pull解析xml的步骤:

public class PullParserUtils
{
    public static List<Person> parserXmlByPull(InputStream inputStream) throws Exception
    {
        List<Person> persons = null;
        Person person = null;
        
        //    建立XmlPullParserFactory解析工厂
        XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
        //    经过XmlPullParserFactory工厂类实例化一个XmlPullParser解析类
        XmlPullParser parser = factory.newPullParser();
        //    根据指定的编码来解析xml文档
        parser.setInput(inputStream, "utf-8");
        
        //    获得当前的事件类型
        int eventType = parser.getEventType();
        //    只要没有解析到xml的文档结束,就一直解析
        while(eventType != XmlPullParser.END_DOCUMENT)
        {
            switch (eventType)
            {
                //    解析到文档开始的时候
                case XmlPullParser.START_DOCUMENT:
                     persons = new ArrayList<Person>();
                break;
                //    解析到xml标签的时候
                case XmlPullParser.START_TAG:
                     if("person".equals(parser.getName()))
                     {
                         person = new Person();
                         //    获得person元素的第一个属性,也就是ID
                         person.setId(Integer.parseInt(parser.getAttributeValue(0)));
                     }
                     else if("name".equals(parser.getName()))
                     {
                         //    若是是name元素,则经过nextText()方法获得元素的值
                         person.setName(parser.nextText());
                     }
                     else if("age".equals(parser.getName()))
                     {
                         person.setAge(Integer.parseInt(parser.nextText()));
                     }
                break;
                //    解析到xml标签结束的时候
                case XmlPullParser.END_TAG:
                     if("person".equals(parser.getName()))
                     {
                         persons.add(person);
                         person = null;
                     }
                break;
            }
            //    经过next()方法触发下一个事件
            eventType = parser.next();
        }
        
        return persons;
    }
}

最后咱们再编写一个HttpUtils类来访问咱们的服务器端的xml文档:

public class HttpUtils
{
    public static InputStream httpMethod(String path, String encode)
    {
        HttpClient httpClient = new DefaultHttpClient();
        
        try
        {
            HttpPost httpPost = new HttpPost(path);
            HttpResponse httpResponse = httpClient.execute(httpPost);
            if(httpResponse.getStatusLine().getStatusCode() == HttpStatus.SC_OK)
            {
                HttpEntity httpEntity = httpResponse.getEntity();
                return httpEntity.getContent();
            }
        }
        catch (Exception e)
        {
            e.printStackTrace();
        }
        finally
        {
            httpClient.getConnectionManager().shutdown();
        }
        
        return null;
    }
}

最后来看看咱们的Android应用程序的布局文件以及Activity类的代码:

public class MainActivity extends Activity
{
    private Button button;
    private Button button2;
    private Button button3;
    private final String PATH = "http://172.25.152.34:8080/httptest/person.xml";
    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        button = (Button)findViewById(R.id.button1);
        button2 = (Button)findViewById(R.id.button2);
        button3 = (Button)findViewById(R.id.button3);
        
        ButtonOnClickListener listener = new ButtonOnClickListener();
        button.setOnClickListener(listener);
        button2.setOnClickListener(listener);
        button3.setOnClickListener(listener);
    }
    
    class ButtonOnClickListener implements OnClickListener
    {
        @Override
        public void onClick(View v)
        {
            Button button = (Button)v;
            switch (button.getId())
            {
                case R.id.button1:
                    //    启动一个新线程解析xml
                    class MyThread1 extends Thread
                    {
                        @Override
                        public void run()
                        {
                            InputStream inputStream = HttpUtils.httpMethod(PATH, "utf-8");
                            List<Person> persons = null;
                            try
                            {
                                persons = DomParserUtils.parserXmlByDom(inputStream);
                            }
                            catch (Exception e)
                            {
                                e.printStackTrace();
                            }
                            System.out.println("dom --->>" + persons);
                        }
                    }
                    new MyThread1().start();
                break;
                case R.id.button2:
                    //    启动一个新线程解析xml
                    class MyThread2 extends Thread
                    {
                        @Override
                        public void run()
                        {
                            InputStream inputStream = HttpUtils.httpMethod(PATH, "utf-8");
                            List<Person> persons = null;
                            try
                            {
                                persons = SaxParserUtils.parserXmlBySax(inputStream);
                            }
                            catch (Exception e)
                            {
                                e.printStackTrace();
                            }
                            System.out.println("sax --->>" + persons);
                        }
                    }
                    new MyThread2().start();
                break;
                case R.id.button3:
                    //    启动一个新线程解析xml
                    class MyThread3 extends Thread
                    {
                        @Override
                        public void run()
                        {
                            InputStream inputStream = HttpUtils.httpMethod(PATH, "utf-8");
                            List<Person> persons = null;
                            try
                            {
                                persons = PullParserUtils.parserXmlByPull(inputStream);
                            }
                            catch (Exception e)
                            {
                                e.printStackTrace();
                            }
                            System.out.println("pull: --->>" + persons);
                        }
                    }
                    new MyThread3().start();
                break;
            }
        }
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu)
    {
        getMenuInflater().inflate(R.menu.main, menu);
        return true;
    }

}

最后咱们来看看控制台的输出:

 

 

总结:dom方式解析xml,比较简单,并能够访问兄弟元素,可是须要将整个xml文档加载到内存中,对于android设备来讲,不推荐使用dom的方式解析xml。

sax和pull都是基于事件驱动的xml解析器,在解析xml时并不会加载整个的xml文档,占用内存较少,所以在android开发中建议使用sax或者pull来解析xml文档。

相关文章
相关标签/搜索