通常情况下,每个需要访问网络的应用程序都会有一个自己的服务器,我们可以向服务器提交数据,也可以从服务器上获取数据。不过这个时候就出现了一个问题,这些数据到底要以什么样的格式在网络上传输呢?随便传递一段文本肯定是不行的,因为另一方根本就不会知道这段文本的用途是什么。因此,一般我们都会在网络上传输一些格式化后的数据,这种数据会有一定的结构规格和语义,当另一方收到数据消息之后就可以按照相同的结构规格进行解析,从而取出他想要的那部分内容。
在网络上传输数据时最常用的格式有两种, 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开源项目“爱阅”。
感谢优秀的你跋山涉水看到了这里,欢迎关注下让我们永远在一起!