26
2017
09

4.1 解析 XML 数据的三种方式详解

点此进入:从零快速构建APP系列目录导图
点此进入:UI编程系列目录导图
点此进入:四大组件系列目录导图
点此进入:数据网络和线程系列目录导图

通常情况下,每个需要访问网络的应用程序都会有一个自己的服务器,我们可以向服务器提交数据,也可以从服务器上获取数据。不过这个时候就出现了一个问题,这些数据到底要以什么样的格式在网络上传输呢?随便传递一段文本肯定是不行的,因为另一方根本就不会知道这段文本的用途是什么。因此,一般我们都会在网络上传输一些格式化后的数据,这种数据会有一定的结构规格和语义,当另一方收到数据消息之后就可以按照相同的结构规格进行解析,从而取出他想要的那部分内容。

在网络上传输数据时最常用的格式有两种, XML 和 JSON,下面我们就来一个个地进行学习,本篇首先学一下如何解析 XML 格式的数据。

本节例程下载地址:WillFLowXML

一、XML数据要点介绍

1、首先介绍一下xml语言

可扩展标记语言 (Extensible Markup Language, XML) ,用于标记电子文件使其具有结构性的标记语言,可以用来标记数据、定义数据类型,是一种允许用户对自己的标记语言进行定义的源语言。

2、xml的语法

XML 分为两部分:头信息,主体信息
  • 头信息是用来描述 XML 的一些属性,例如:版本,编码等,还可以提供 XML 显示的样式,和 dtd 编写格式。
  • 主体信息中包含的是 XML 的具体数据。
头信息的语法:
<?xml version =”1.0” encoding =”GBK” ?>

其中 version 是必须加的,而 encoding 可以不写,则默认编码是 ISO8859-1 ,不支持中文。除了这个功能外,头信息还可以进行编写格式的规定,通过 dtd 或 xsd 文件。头信息还支持样式表的导入,允许通过样式表控制 XML 的显示。

这样可以使用 XML+ CSS 完成页面的显示,通过这种形式完成 MVC 中的 View 层:
- 优点:代码的安全性很高,可以很容易的替换模板。
- 缺点:开发成本太高。

二、XML常用三种解析方式的优缺点

(1)DOM(Document Object Model)

文档对象模型分析方式。以层次结构(类似于树型)来组织节点和信息片段,映射XML文档的结构,允许获取和操作文档的任意部分,是W3C的官方标准。

优点:
1、允许应用程序对数据和结构做出更改。
2、访问是双向的,可以在任何时候在树中上下导航,获取和操作任意部分的数据。

缺点:
通常需要加载整个XML文档来构造层次结构,消耗资源大

(2)SAX(Simple API for XML)

流模型中的推模型分析方式。通过事件驱动,每发现一个节点就引发一个事件,通过回调方法完成解析工作,解析XML文档的逻辑需要应用程序完成。

优点:
1、不需要等待所有数据都被处理,分析就能立即开始。
2、只在读取数据时检查数据,不需要保存在内存中。
3、可以在某个条件得到满足时停止解析,不必解析整个文档。
4、效率和性能较高,能解析大于系统内存的文档。

缺点:
1、需要应用程序自己负责TAG的处理逻辑(例如维护父/子关系等),使用麻烦。
2、单向导航,很难同时访问同一文档的不同部分数据,不支持XPath。

(3)XMLPull解析

一种基于事件流的解析方案、类似于SAX解析、Android中推荐的一种解析方案、Android 中内置了XMLPull解析的API。

Pull解析和Sax解析不一样的地方有:
1、pull读取xml文件后触发相应的事件调用方法返回的是数字
2、pull可以在程序中控制想解析到哪里就可以停止解析。

三、用三种方式解析XML

1、准备工作

首先,我们在主界面定义好四个按钮以及用于内容展示的ListView,代码如下:
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="com.wgh.willflowxml.MainActivity">

    <Button  android:id="@+id/button_dom" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="DOM解析" android:textColor="#0787ff" android:textSize="22dp" />

    <Button  android:id="@+id/button_sax" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="SAX解析" android:textColor="#f13232" android:textSize="22dp" />

    <Button  android:id="@+id/button_pull" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="PULL解析" android:textColor="#fc7711" android:textSize="22dp" />

    <ListView  android:id="@+id/list_view" android:layout_width="356dp" android:layout_height="437dp" android:layout_marginBottom="8dp" android:layout_marginTop="8dp">
    </ListView>

</android.support.constraint.ConstraintLayout>
然后修改 MainActivity 代码如下:
public class MainActivity extends AppCompatActivity implements View.OnClickListener{

    private Button mButtonDom;
    private Button mButtonSax;
    private Button mButtonPull;
    private ListView mListView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        initView();
    }

    private void initView() {
        mButtonDom = (Button) findViewById(R.id.button_dom);
        mButtonSax = (Button) findViewById(R.id.button_sax);
        mButtonPull = (Button) findViewById(R.id.button_pull);
        mListView = (ListView) findViewById(R.id.list_view);

        mButtonDom.setOnClickListener(this);
        mButtonSax.setOnClickListener(this);
        mButtonPull.setOnClickListener(this);
    }

    @Override
    public void onClick(View view) {
        switch (view.getId()) {
            case R.id.button_dom:

                break;
            case R.id.button_sax:

                break;
            case R.id.button_pull:

                break;
        }
    }
}

可以看到我们在这里面找到了个控件,并为三个按钮分别添加了点击监听器。

接着,我们在assets目录下定义三个用于解析的XML源文件,分别为:
  • person1:
<?xml version="1.0" encoding="UTF-8"?>
<persons>
    <person id = "10">
        <name>Dom解析</name>
        <age>16</age>
    </person>
    <person id = "13">
        <name>WillFlow1</name>
        <age>18</age>
    </person>
</persons>
  • person2:
<?xml version="1.0" encoding="UTF-8"?>
<persons>
    <person id = "11">
        <name>Sax解析</name>
        <age>18</age>
    </person>
    <person id = "13">
        <name>WillFlow2</name>
        <age>17</age>
    </person>
</persons>
  • person3:
<?xml version="1.0" encoding="UTF-8"?>
<persons>
    <person id = "11">
        <name>PULL解析</name>
        <age>18</age>
    </person>
    <person id = "13">
        <name>WillFlow3</name>
        <age>16</age>
    </person>
</persons>
最后,我们定义一个Person数据类,用于解析数据的模板:
public class Person {
    private int id;
    private String name;
    private int age;

    public Person() {

    }

    public Person(int id, String name, int age) {
        this.id = id;
        this.name = name;
        this.age = age;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "姓名 : " + this.name + ", 年龄 : " + this.age;
    }
}

准备工作就这样做好了,接下来我们分别用三种方法进行解析吧。

2、实现DOM解析

我们定义一个DOMHelper解析类,代码如下:
/** * Created by : WGH. */
public class DomHelper {
    public static ArrayList<Person> queryXML(Context context) {
        ArrayList<Person> Persons = new ArrayList<Person>();
        try {
            // 获得DOM解析器的工厂示例
            DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
            // 从Dom工厂中获得dom解析器
            DocumentBuilder dbBuilder = dbFactory.newDocumentBuilder();
            // 把要解析的xml文件读入Dom解析器
            Document document = dbBuilder.parse(context.getAssets().open("person1.xml"));
            Log.i(TAG, "DomImplemention : " + document.getImplementation());
            // 得到文档中名称为person的元素的结点列表
            NodeList nodeList = document.getElementsByTagName("person");
            // 遍历该集合,显示集合中的元素以及子元素的名字
            for (int i = 0; i < nodeList.getLength(); i++) {
                // 先从Person元素开始解析
                Element personElement = (Element) nodeList.item(i);
                Person person = new Person();
                person.setId(Integer.valueOf(personElement.getAttribute("id")));

                // 获取person下的name和age的Note集合
                NodeList childNoList = personElement.getChildNodes();
                for (int j = 0; j < childNoList.getLength(); j++) {
                    Node childNode = childNoList.item(j);
                    // 判断子note类型是否为元素Note
                    if (childNode.getNodeType() == Node.ELEMENT_NODE) {
                        Element childElement = (Element) childNode;
                        if ("name".equals(childElement.getNodeName()))
                            person.setName(childElement.getFirstChild().getNodeValue());
                        else if ("age".equals(childElement.getNodeName()))
                            person.setAge(Integer.valueOf(childElement.getFirstChild().getNodeValue()));
                    }
                }
                Persons.add(person);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return Persons;
    }
}

可以看到,我们在这里首先获得了DOM解析器的工厂示例,并从工厂示例中获得了dom解析器,最后把要解析的xml文件读入Dom解析器,解析的过程如代码中的注释所示。

然后在 MainActivity 中的电机监听器中添加如下代码进行解析和展示:
                DomHelper domHelper = new DomHelper();
                mPersonArrayList = domHelper.queryXML(getApplicationContext());
                mAdapter = new ArrayAdapter<Person>(MainActivity.this, android.R.layout.simple_expandable_list_item_1, mPersonArrayList);
                mListView.setAdapter(mAdapter);
编译运行看效果

3、实现SAX解析

我们定义一个SAXHelper解析类,代码如下:
/** * Created by : WGH. */
public class SaxHelper extends DefaultHandler {
    private Person mPerson;
    private ArrayList<Person> personArrayList;
    // 当前解析的元素标签
    private String tagName = null;

    /** * 当读取到文档开始标志是触发,通常在这里完成一些初始化操作 */
    @Override
    public void startDocument() throws SAXException {
        this.personArrayList = new ArrayList<Person>();
        Log.i(TAG, "读取到文档头,开始解析XML");
    }

    /** * 读到一个开始标签时调用,第二个参数为标签名,最后一个参数为属性数组 */
    @Override
    public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
        if (localName.equals("person")) {
            mPerson = new Person();
            mPerson.setId(Integer.parseInt(attributes.getValue("id")));
            Log.i(TAG, "开始处理person元素");
        }
        this.tagName = localName;
    }


    /** * 读到到内容,第一个参数为字符串内容,后面依次为起始位置与长度 */
    @Override
    public void characters(char[] ch, int start, int length)
            throws SAXException {
        // 判断当前标签是否有效
        if (this.tagName != null) {
            String data = new String(ch, start, length);
            // 读取标签中的内容
            if (this.tagName.equals("name")) {
                this.mPerson.setName(data);
                Log.i(TAG, "处理name元素内容");
            } else if (this.tagName.equals("age")) {
                this.mPerson.setAge(Integer.parseInt(data));
                Log.i(TAG, "处理age元素内容");
            }

        }

    }

    /** * 处理元素结束时触发,这里将对象添加到结合中 */
    @Override
    public void endElement(String uri, String localName, String qName)
            throws SAXException {
        if (localName.equals("person")) {
            this.personArrayList.add(mPerson);
            mPerson = null;
            Log.i(TAG, "处理person元素结束");
        }
        this.tagName = null;
    }

    /** * 读取到文档结尾时触发, */
    @Override
    public void endDocument() throws SAXException {
        super.endDocument();
        Log.i(TAG, "读取到文档尾,XML解析结束");
    }

    // 获取persons集合
    public ArrayList<Person> getPersonArrayList() {
        return personArrayList;
    }
}
然后定义这样一个方法用于SAX方式解析:
    private ArrayList<Person> readxmlBySAX() throws Exception {
        // 获取文件资源建立输入流对象
        InputStream inputStream = getAssets().open("person2.xml");
        // 创建XML解析处理器
        SaxHelper saxHelper = new SaxHelper();
        // 得到SAX解析工厂
        SAXParserFactory factory = SAXParserFactory.newInstance();
        // 创建SAX解析器
        SAXParser parser = factory.newSAXParser();
        // 将xml解析处理器分配给解析器,对文档进行解析,将事件发送给处理器
        parser.parse(inputStream, saxHelper);
        inputStream.close();
        return saxHelper.getPersons();
    }
最后再点击监控器里面这样使用:
                try {
                    mPersonArrayList = readxmlBySAX();
                } catch (Exception e) {
                    e.printStackTrace();
                }
                mAdapter = new ArrayAdapter<Person>(MainActivity.this, android.R.layout.simple_expandable_list_item_1, mPersonArrayList);
                mListView.setAdapter(mAdapter);
运行开效果

4、实现PULL解析

首先,我们实现一个帮助类代码如下:
/** * Created by : WGH. */
public class PullHelper {
    public static ArrayList<Person> getPersons(InputStream xml) throws Exception {
        // XmlPullParserFactory pullPaser = XmlPullParserFactory.newInstance();
        ArrayList<Person> personArrayList = null;
        Person person = null;
        // 创建一个xml解析的工厂
        XmlPullParserFactory pullParserFactory = XmlPullParserFactory.newInstance();
        // 获得xml解析类的引用
        XmlPullParser parser = pullParserFactory.newPullParser();
        parser.setInput(xml, "UTF-8");
        // 获得事件的类型
        int eventType = parser.getEventType();
        while (eventType != XmlPullParser.END_DOCUMENT) {
            switch (eventType) {
                case XmlPullParser.START_DOCUMENT:
                    personArrayList = new ArrayList<Person>();
                    break;
                case XmlPullParser.START_TAG:
                    if ("person".equals(parser.getName())) {
                        person = new Person();
                        // 取出属性值
                        int id = Integer.parseInt(parser.getAttributeValue(0));
                        person.setId(id);
                    } else if ("name".equals(parser.getName())) {
                        String name = parser.nextText();// 获取该节点的内容
                        person.setName(name);
                    } else if ("age".equals(parser.getName())) {
                        int age = Integer.parseInt(parser.nextText());
                        person.setAge(age);
                    }
                    break;
                case XmlPullParser.END_TAG:
                    if ("person".equals(parser.getName())) {
                        personArrayList.add(person);
                        person = null;
                    }
                    break;
            }
            eventType = parser.next();
        }
        return personArrayList;
    }

    public static void saveXML(List<Person> persons, OutputStream out) throws Exception {
        XmlSerializer serializer = Xml.newSerializer();
        serializer.setOutput(out, "UTF-8");
        serializer.startDocument("UTF-8", true);
        serializer.startTag(null, "persons");
        for (Person p : persons) {
            serializer.startTag(null, "person");
            serializer.attribute(null, "id", p.getId() + "");
            serializer.startTag(null, "name");
            serializer.text(p.getName());
            serializer.endTag(null, "name");
            serializer.startTag(null, "age");
            serializer.text(p.getAge() + "");
            serializer.endTag(null, "age");
            serializer.endTag(null, "person");
        }
        serializer.endTag(null, "persons");
        serializer.endDocument();
        out.flush();
        out.close();
    }
}
然后再 MainActivity 的电击监听器里面这样定义代码:
                // 获取文件资源建立输入流对象
                try {
                    InputStream is = getAssets().open("person3.xml");
                    mPersonArrayList = PullHelper.getPersons(is);
                    if (mPersonArrayList == null) {
                        Toast.makeText(getApplicationContext(), "读取出错!", Toast.LENGTH_SHORT).show();
                    }
                    for (Person p1 : mPersonArrayList) {
                        Log.i(TAG, p1.toString());
                    }
                    mAdapter = new ArrayAdapter<Person>(MainActivity.this, android.R.layout.simple_expandable_list_item_1, mPersonArrayList);
                    mListView.setAdapter(mAdapter);
                } catch (Exception e) {
                    e.printStackTrace();
                }
编译运行看效果

注意:我们这里定义了一种写入数据的方法,你可以在需要的时候这样使用:

    private void saveFileByPull() {
        List<Person> persons = new ArrayList<Person>();
        persons.add(new Person(66, "WillFLow1", 16));
        persons.add(new Person(77, "WillFLow2", 17));
        persons.add(new Person(88, "WillFLow3", 18));
        File xmlFile = new File(getApplicationContext().getFilesDir(), "WillFlow.xml");
        FileOutputStream fileOutputStream;
        try {
            fileOutputStream = new FileOutputStream(xmlFile);
            PullHelper.saveXML(persons, fileOutputStream);
            Toast.makeText(getApplicationContext(), "文件写入完毕", Toast.LENGTH_SHORT).show();
        } catch (Exception e) {
            e.printStackTrace();
        }   
    }

点此进入:GitHub开源项目“爱阅”,下面是“爱阅”的效果图:


联系方式:

简书:WillFlow
CSDN:WillFlow
微信公众号:WillFlow

微信公众号:WillFlow

上一篇:3.2 使用 URL 类请求和提交数据详解 下一篇:UICC之UiccCardApplication