Android网络之数据解析----SAX方式解析XML数据

简介:

【正文】

一、XML和Json数据的引入:

通常情况下,每个需要访问网络的应用程序都会有一个自己的服务器,我们可以向服务器提交数据,也可以从服务器获取数据。不过这个时候就有一个问题,这些数据是以什么格式在网络上传输的呢?一般我们都会在网络上传输一些格式化后的数据,这种数据会有一定的结构规格和语言,当另一方受到数据消息后就可以按照相同的结构规格进行解析,从而取出它想要的那部分内容

在网络上传输数据最常用的格式:XML和Json。本文就来学习一下XML数据的解析,Json格式的数据解析将在下一篇文章中讲到。

 

二、XML的介绍:

XML,可扩展标记语言 (Extensible Markup Language) ,用于标记电子文件使其具有结构性的标记语言,可以用来标记数据、定义数据类型,是一种允许用户对自己的标记语言进行定义的源语言,这是百度百科的解释。而XML是一种在Internet中传输数据的常见格式,它与HTML一样,都是SGML(标准通用标记语言),无论你是需要通过Internet访问数据,或者发送数据给Web服务,都可能需要用到XML的知识。恰恰Android应用程序需要和网络交互,否则只是一款单机的无互动的应用程序,所以在Android应用程序开发的过程中使用到XML是很有必要的。

由于XML的扩展性强,致使它需要有稳定的基础规则来支持扩展,该语法规则需要注意的是:

  • 开始和结束标签匹配。
  • 嵌套标签不能相互嵌套。
  • 区分大小写。

XML的结构解析如下:

  • 节点
  • 元素
  • 属性和属性值

格式如下:

<标记名称 属性名1="属性值1" 属性名1="属性值1" ……>内容</标记名称>

 

三、Android中的XML解析的分类:

Android平台最大的优势在于,上层应用基本可以利用Java编程语言开发,Java平台支持通过许多不同的方式来使用XML,并且大多数与XML相关的API已经在Android系统上得到了完全的支持。但是因为Android这个移动设备的局限性,一般仅考虑使用三种方式解析XML:

  • DOM,Document Object Model,文档对象模型方式,解析完的XML将生成一个树状结构的对象。
  • SAX,simple API for  Xml,以事件的形式通知程序,对XML进行解析。
  • XML PULL,类似于SAX方式,程序以拉取的方式对XML进行解析。

 

四、SAX解析介绍:

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

使用SAX的优点:

因为SAX的优势是流的方式处理,当遇到一个标签的时候,并不会记录下之前所碰到的标签。也就是说,在每个节点读取会触发的startElement()方法中,所能知道的信息,仅仅是当前的签名的名字和属性,至于标签嵌套的结构,上层标签的名字,是否有子元素与其他结构相关的信息,都是不知道的。

使用SAX解析XML的简单步骤:

  • 新建一个类MyHandler,继承自DefaultHandler,并重写DefaultHandler中的特有方法,解析XML的工作在此类中完成。
  • 实例化一个SAX解析器的工厂对象,SAXParserFactory对象,使用SAXParserFactory.newInstance()方法获取。
  • 利用SAXParserFactory.newSAXParser()获得SAX解析器对象SAXParser。
  • 实例化MyHandler类,传入需要解析的节点名称。
  • 使用SAXParser.parse()方法设置待解析的XML流和XML解析对象。
  • 最后从MyHandler对象中获得解析结果。

现在详细讲解一下上面提到的类的作用:

DefaultHandler类是SAX2事件处理程序的默认基类。它继承了EntityResolver、DTDHandler、ContentHandler和ErrorHandler这四个接口。包含这四个接口的所有方法,所以我们在编写事件处理程序时,可以不用直接实现这四个接口,而继承该类,然后重写我们需要的方法。

而在继承DefaultHandler的类中,需要重写以下五个方法:

复制代码
public void startDocument()
当遇到文档的开头的时候,调用这个方法,可以在其中做一些预处理的工作。 

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

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

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

public void endDocument()
和startDocument()方法相对应。当文档结束的时候,调用这个方法,可以在其中做一些善后的工作。 
复制代码

我们通过一个XML文件来讲解一下上面的五个方法在什么时候被执行:

<?xml version="1.0" encoding="utf-8"?>                  startDocument

<persons>                                                             startElement

  <person id="01">                                             startElement

      <name nameid="1">                                   startElement

        smyh                                                         characters

        </name>                                                   endElement

           <age>                                                        startElement

            22                                                             characters

          </age>                                                        endElement

  </person>                      endElement       

</persons>                        endElement

SAXParserFactory类,定义了一个工厂API,使应用程序能够配置和获得基于SAX的解析器以解析XML文档。它只有一个protected的构造方法(单例模式),所以需要使用静态的newInstance()方法来回的SAXParserFactory()对象。使用SAXParserFactory可以通过调用.newSAXParser()方法获得一个SAXParser,通过SAXParser对象可以执行parser()方法,通过传递的参数设定XML流和解析器类。

 

五、SAX解析XML的步骤:(代码实现)

现在通过一个示例程序来讲解一下SAX是怎么解析XML文件的,这个示例程序是运行在Android平台上的,为了模拟真实情况,在tomcat服务器上放置了一个静态的XML文件,即在D:\apache-tomcat-8.0.14\webapps\ROOT目录中新建一个smyhvae.xml文件,xml文件内容如下:

复制代码
<?xml version="1.0" encoding="UTF-8"?>
<persons>
    <person id="01">
        <name>smyh</name>
        <age>22</age>
    </person>
    <person id="02">
        <name>vae</name>
        <age>24</age>
    </person>
</persons>
复制代码

注:关于tomcat服务器的配置,如果不清楚的话,请参照本人另外一篇博客:Android系列之网络(三)----使用HttpClient发送HTTP请求(分别通过GET和POST方法发送数据)

因为我电脑的IP地址是192.168.1.112。现在我们在浏览器输入http://192.168.1.112:8080/smyhvae.xml,显示效果如下:

现在我们需要做的是:通过Android程序去获取并解析这段XML数据。在这个示例程序中,读取person节点的值。因为是Android程序,所以别忘了赋予其访问网络的权限。

整个Android的工程结构如下:

(1)【新建工具类HttpUtils通过URLHttpConnection获取服务器上的XML流

我们将其写成工具类,代码如下:

复制代码
 1 package com.example.androidsaxxml.http;
 2 
 3 import java.io.InputStream;
 4 import java.net.HttpURLConnection;
 5 import java.net.URL;
 6 
 7 //工具类:通过URLHttpConnection获取服务器上的XML流
 8 public class HttpUtils {
 9 
10     public HttpUtils() {
11     }
12     
13     //方法:返回的InputStream对象就是服务器返回的XML流。
14     public static InputStream getXML(String path) {//参数path:之后将在MainActivity中指定具体的url链接
15         try {
16             URL url=new URL(path);
17             if(url!=null)
18             {
19                 HttpURLConnection connection=(HttpURLConnection)url.openConnection();
20                 connection.setDoInput(true);
21                 connection.setConnectTimeout(3000);
22                 connection.setRequestMethod("GET");
23                 int requesetCode=connection.getResponseCode();
24                 if(requesetCode==200)
25                 {
26                     //如果执行成功,返回HTTP响应流
27                     return connection.getInputStream();
28                 }
29             }
30         } catch (Exception e) {
31             // TODO: handle exception
32         }        
33         return null;
34     }
35 } 
复制代码

(2)【新建类MyHandler】新建子类MyHandler,继承DefaultHandler类:用来解析xml

sax解析xml最重要的步骤就是定义一个我们自己的Handler处理类,并让其继承 DefaultHandler 这个类,然后在里面重写其回调方法,在这些回调方法里来做我们的xml解析。代码如下:

复制代码
 1 package com.example.androidsaxxml.handler;
 2 
 3 import java.util.ArrayList;
 4 import java.util.HashMap;
 5 import java.util.List;
 6 
 7 import org.xml.sax.Attributes;
 8 import org.xml.sax.SAXException;
 9 import org.xml.sax.helpers.DefaultHandler;
10 
11 
12 //类:MyHandler,继承DefaultHandler类,用于解析XML数据。
13 //之后在MainActivity中通过设定具体的nodeName来实例化MyHandler
14 public class MyHandler extends DefaultHandler {
15     private List<HashMap<String, String>> list = null; //解析后的XML内容
16     private HashMap<String, String> map = null;  //存放当前需要记录的节点的XML内容
17     private String currentTag = null;//当前读取的XML节点
18     private String currentValue = null;//当前节点的XML文本值
19     private String nodeName = null;//需要解析的节点名称
20     
21     public MyHandler(String nodeName) {
22         // 设置需要解析的节点名称
23         this.nodeName = nodeName;
24     }
25     
26     @Override
27     public void startDocument() throws SAXException {
28         // 接收文档开始的通知
29         // 实例化ArrayList用于存放解析XML后的数据
30         list = new ArrayList<HashMap<String, String>>();
31     }
32     
33     @Override
34     public void startElement(String uri, String localName, String qName,
35             Attributes attributes) throws SAXException {
36         // 接收元素开始的通知        
37         if (qName.equals(nodeName)) {
38             //如果当前运行的节点名称与设定需要读取的节点名称相同,则实例化HashMap
39             map = new HashMap<String, String>();
40         }
41         //Attributes为当前节点的属性值,如果存在属性值,则属性值也读取。
42         if (attributes != null && map != null) {
43             for (int i = 0; i < attributes.getLength(); i++) {
44                 //读取到的属性值,插入到Map中。
45                 map.put(attributes.getQName(i), attributes.getValue(i));
46             }
47         }
48         //记录当前节点的名称。
49         currentTag = qName;
50     }
51     
52     @Override
53     public void characters(char[] ch, int start, int length)
54             throws SAXException {
55         // 接收元素中字符数据的通知。
56         //当前节点有值的情况下才继续执行
57         if (currentTag != null && map != null) {
58             //获取当前节点的文本值,ch这个直接数组就是存放的文本值。
59             currentValue = new String(ch, start, length);
60             if (currentValue != null && !currentValue.equals("")
61                     && !currentValue.equals("\n")) {
62                 //读取的文本需要判断不能为null、不能等于”“、不能等于”\n“
63                 map.put(currentTag, currentValue);
64             }
65         }
66         //读取完成后,需要清空当前节点的标签值和所包含的文本值。
67         currentTag = null;
68         currentValue = null;
69     }
70     
71     @Override
72     public void endElement(String uri, String localName, String qName)
73             throws SAXException {
74         // 接收元素结束的通知。
75         if (qName.equals(nodeName)) {
76             //如果读取的结合节点是我们需要关注的节点,则把map加入到list中保存
77             list.add(map);
78             //使用之后清空map,开始新一轮的读取person。
79             map = null;
80         }
81     }
82     
83     //方法:获取解析之后的数据
84     public List<HashMap<String, String>> getList() {
85         return list;
86     }
87 }
复制代码

(3)【新建类SaxService】实例化一个SAX解析器的工厂对象:SAXParserFactory

需要一个调用SAXParser对象的类,这里新建一个SaxService类,实例化SAXParserFactory用于设定XML流和解析器,也就是在这里调用了上一步中的MyHandler类。代码如下:

复制代码
 1 package com.example.androidsaxxml.service;
 2 
 3 import java.io.InputStream;
 4 import java.util.HashMap;
 5 import java.util.List;
 6 
 7 import javax.xml.parsers.SAXParser;
 8 import javax.xml.parsers.SAXParserFactory;
 9 
10 import com.example.androidsaxxml.handler.MyHandler;
11 
12 
13 //类:用于实例化例化一个SAX解析器的工厂对象:SAXParserFactory
14 public class SaxService {
15 
16     public SaxService() {
17         // TODO Auto-generated constructor stub
18     }
19     
20     //方法:解析xml数据并返回,返回值类型是HashMap
21     public static List<HashMap<String, String>> readXML(InputStream inputStream,String nodeName)
22     {
23         try {
24             //实例化SAX工厂类
25             SAXParserFactory factory=SAXParserFactory.newInstance();
26             //实例化SAX解析器。
27             SAXParser sParser=factory.newSAXParser();
28             //实例化工具类MyHandler,设置需要解析的节点
29             MyHandler myHandler=new MyHandler(nodeName);
30             // 开始解析
31             sParser.parse(inputStream, myHandler);
32             // 解析完成之后,关闭流
33             inputStream.close();
34             //返回解析结果。
35             return myHandler.getList();  //在这里返回解析之后的数据
36         } catch (Exception e) {
37             // TODO: handle exception
38         }        
39         return null;
40     }
41     
42 } 
复制代码

核心代码是第29行和第31行。

(4)在MainActicity中实例化:即实例化需要访问的链接path和需要解析的节点nodeName

布局界面很简单,只有一个按钮控件,这里就不展示布局代码了。点击按钮后,触发点击事件,因为是Android4.0+,所以不能在主线程中访问网络,需要另起一个线程,这里使用Thread类。代码如下: 

复制代码
 1 package com.example.androidsaxxml;
 2 
 3 import java.io.InputStream;
 4 import java.util.HashMap;
 5 import java.util.List;
 6 
 7 import android.app.Activity;
 8 import android.os.Bundle;
 9 import android.view.View;
10 import android.widget.Button;
11 
12 import com.example.androidsaxxml.http.HttpUtils;
13 import com.example.androidsaxxml.service.SaxService;
14 
15 
16 public class MainActivity extends Activity {
17     private Button button;
18     @Override
19     protected void onCreate(Bundle savedInstanceState) {
20         super.onCreate(savedInstanceState);
21         setContentView(R.layout.activity_main);
22         
23         button=(Button)findViewById(R.id.button1);
24         button.setOnClickListener(new View.OnClickListener() {
25             
26             @Override
27             //点击按钮,开启线程访问网络
28             public void onClick(View v) {
29                 Thread thread=new Thread(new Runnable() {
30                     
31                     @Override
32                     public void run() {
33                         // 设置XML文档的路径
34                         String path="http://192.168.1.112:8080/smyhvae.xml";
35                         //调用类HttpUtils:从服务器上获取XML流。
36                         InputStream inputStream=HttpUtils.getXML(path);
37                         try {
38                             //调用类SaxService:解析流,同时设定需要解析的节点
39                             List<HashMap<String, String>> list=SaxService.readXML(inputStream, "person");
40                             for(HashMap<String,String> map:list)
41                             {
42                                 //打印到LogCat中
43                                 System.out.println(map.toString());
44                             }
45                         } catch (Exception e) {
46                             // TODO: handle exception
47                         }
48                     }
49                 });
50                 thread.start();                
51             }
52         });        
53     }
54 }
复制代码

核心代码是第36行(解析具体的url)、39行(从person节点开始读取)。

当点击按钮后,XML解析后的内容会把打印到日志中:

相关文章
|
17天前
|
XML JavaScript 前端开发
xml文件使用及解析
xml文件使用及解析
|
23天前
|
XML Java Android开发
Android实现自定义进度条(源码+解析)
Android实现自定义进度条(源码+解析)
51 1
|
1月前
|
机器学习/深度学习 算法 PyTorch
RPN(Region Proposal Networks)候选区域网络算法解析(附PyTorch代码)
RPN(Region Proposal Networks)候选区域网络算法解析(附PyTorch代码)
229 1
|
1月前
|
缓存 网络协议 Linux
【Shell 命令集合 网络通讯 】Linux 配置DNS dnsconf 命令 使用教程
【Shell 命令集合 网络通讯 】Linux 配置DNS dnsconf 命令 使用教程
39 0
|
4天前
|
XML Java 数据库连接
Javaweb之Mybatis的XML配置文件的详细解析
Javaweb之Mybatis的XML配置文件的详细解析
13 0
|
7天前
|
XML C# 数据格式
C# 解析XML文件
C# 解析XML文件
15 1
|
10天前
|
Android开发 开发者
Android网络和数据交互: 请解释Android中的AsyncTask的作用。
Android&#39;s AsyncTask simplifies asynchronous tasks for brief background work, bridging UI and worker threads. It involves execute() for starting tasks, doInBackground() for background execution, publishProgress() for progress updates, and onPostExecute() for returning results to the main thread.
10 0
|
10天前
|
网络协议 安全 API
Android网络和数据交互: 什么是HTTP和HTTPS?在Android中如何进行网络请求?
HTTP和HTTPS是网络数据传输协议,HTTP基于TCP/IP,简单快速,HTTPS则是加密的HTTP,确保数据安全。在Android中,过去常用HttpURLConnection和HttpClient,但HttpClient自Android 6.0起被移除。现在推荐使用支持TLS、流式上传下载、超时配置等特性的HttpsURLConnection进行网络请求。
10 0
|
15天前
|
存储 安全 测试技术
网络奇谭:虚拟机中的共享、桥接与Host-Only模式解析
网络奇谭:虚拟机中的共享、桥接与Host-Only模式解析
19 0
|
24天前
|
XML Java Android开发
Android每点击一次按钮就添加一条数据
Android每点击一次按钮就添加一条数据
24 1

推荐镜像

更多