android开发一般对xml操作常用三种技术:sax、dom、pull
分别详细的进行介绍:
首先创建开发测试坏境(一下三种方法都会使用这个环境):
在类路径下面创建xml文件:
person.xml
<?xml version="1.0" encoding="UTF-8"?>
<persons>
<person id="1">
<name>王昌龙</name>
<age>23</age>
</person>
<person id="3">
<name>小妾</name>
<age>17</age>
</person>
</persons>
(我的名字and因为没有媳妇,就假设叫小妾)
针对person创建javabean,(因为我们下面要以对象的形式获取此xml文件内容)
package cn.partner4java.xml.bean;
public class Person {
private int id;
private String name;
private short 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 short getAge() {
return age;
}
public void setAge(short age) {
this.age = age;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + id;
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Person other = (Person) obj;
if (id != other.id)
return false;
return true;
}
}
sax:
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)就可以获取内容。
不知道你看懂上面的解释了么?那好,我们动手做一下:
创建XMLContentHandler,如上面说的应该实现 ContentHandler接口,但是我们这里去集成一个它的实现类就OL,
package cn.partner4java.sax.service;
import java.util.ArrayList;
import java.util.List;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
import cn.partner4java.sax.bean.Person;
public class XMLContentHandler extends DefaultHandler {
private List<Person> persons;
private Person person;
private String preTag;
public List<Person> getPersons() {
return persons;
}
//这个方法用来处理在XML文件中读到的内容,第一个参数为文件的字符串内容,后面两个参数是读到的字符串在这个数组中的起始位置和长度,
//使用new String(ch,start,length)就可以获取内容。
@Override
public void characters(char[] ch, int start, int length)
throws SAXException {
String data = new String(ch, start, length);
if("name".equals(preTag)){
person.setName(data);
}else if("age".equals(preTag)){
person.setAge(new Short(data));
}
}
//这个方法和上面的方法相对应,在遇到结束标签的时候,调用这个方法。
@Override
public void endElement(String uri, String localName, String qName)
throws SAXException {
if("person".endsWith(localName) && person != null){
persons.add(person);
person = null;
}
preTag = null;
}
//和上面的方法相对应,当文档结束的时候,调用这个方法,可以在其中做一些善后的工作。
@Override
public void startDocument() throws SAXException {
persons = new ArrayList<Person>();
}
//当读到一个开始标签的时候,会触发这个方法。namespaceURI就是命名空间,localName是不带命名空间前缀的标签名,
//qName是带命名空间前缀的标签名。通过atts可以得到所有的属性名和相应的值。要注意的是SAX中一个重要的特点就是它的流式处理,
//当遇到一个标签的时候,它并不会纪录下以前所碰到的标签,也就是说,在startElement()方法中,所有你所知道的信息,就是标签的名字和属性,
//至于标签的嵌套结构,上层标签的名字,是否有子元属等等其它与结构相关的信息,都是不得而知的,
//都需要你的程序来完成。这使得SAX在编程处理上没有DOM来得那么方便。
@Override
public void startElement(String uri, String localName, String qName,
Attributes attributes) throws SAXException {
//初始化:新建javabean
if("person".equals(localName)){
person = new Person();
person.setId(new Integer(attributes.getValue(0)));
}
preTag = localName;
}
}
SAX 支持已内置到JDK1.5中,你无需添加任何的jar文件
书写service代码:
package cn.partner4java.sax.service;
import java.io.InputStream;
import java.util.List;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import cn.partner4java.sax.bean.Person;
public class SAXService {
public static List<Person> readXML(InputStream inStream) {
try {
SAXParserFactory spf = SAXParserFactory.newInstance();
SAXParser saxParser = spf.newSAXParser(); //创建解析器
//设置解析器的相关特性,http://xml.org/sax/features/namespaces = true 表示开启命名空间特性
//saxParser.setProperty("http://xml.org/sax/features/namespaces",true);
XMLContentHandler handler = new XMLContentHandler();
saxParser.parse(inStream, handler);
inStream.close();
return handler.getPersons();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
进行单元测试:
package cn.partner4java.sax.junit;
import java.io.InputStream;
import java.util.List;
import android.test.AndroidTestCase;
import android.util.Log;
import cn.partner4java.sax.bean.Person;
import cn.partner4java.sax.service.SAXService;
public class SAXServiceTest extends AndroidTestCase {
private static String TAG = "SAXServiceTest";
public void testReadXML() {
InputStream inStream = this.getClass().getClassLoader().getResourceAsStream("person.xml");
List<Person> persons = SAXService.readXML(inStream);
for(Person person:persons){
System.out.println(person.getName());
Log.i(TAG, person.getName());
}
}
}
sax的集体操作大体就如上,没有什么太难理解的地方,我们重载的那些方法,你不要想着我们是如何去调用的,不是,他是使用的流程装载,也就说会安照我们开始解释的那个几个方法的作用,安步骤自动往下走,并且方法是重复调用的,因为我们xml里面不止一个数据。
android的单元测试我这里说一下:
因为单元测试也就是黑盒测试吧,有很好的作用,能够帮助我们去调试代码,避免出现很难解决的复杂错误,因为我们基本都是会每隔方法测试后,再继续往下写。
我们在写j2ee的junit是怎么样的我就不说了,
android里面首先,要配置我们的
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="cn.partner4java.sax"
android:versionCode="1"
android:versionName="1.0">
<application android:icon="@drawable/icon" android:label="@string/app_name">
<activity android:name=".SaxDemoActivity"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<uses-library android:name="android.test.runner" />
</application>
<instrumentation android:name="android.test.InstrumentationTestRunner"
android:targetPackage="cn.partner4java.sax" android:label="Tests for My App" />
</manifest>
上面targetPackage指定的包要和应用的package相同。也就说必须相同,但是我们的单元测试可以放到此包的子包里,外面是不行的。
编写单元测试代码(选择要测试的方法,右键点击“Run As”--“Android Junit Test” )
你可能看到了我写了两种打印的方法:
System.out.println(person.getName());
Log.i(TAG, person.getName());
System.out.println在老版本的日志里面是不打印的(我现在用的最新版本可以了),所以只能用 Log。但是这两种打印方式还是都不可以打印中文,会出现乱码
DOM:
除了可以使用 SAX解析XML文件,大家也可以使用熟悉的DOM来解析XML文件。 DOM解析XML文件时,会将XML文件的所有内容读取到内存中,然后允许您使用DOM API遍历XML树、检索所需的数据。使用DOM操作XML的代码看起来比较直观,并且,在某些方面比基于SAX的实现更加简单。但是,因为DOM需要将 XML文件的所有内容读取到内存中,所以内存的消耗比较大,特别对于运行Android的移动设备来说,因为设备的资源比较宝贵,所以建议还是采用SAX 来解析XML文件,当然,如果XML文件的内容比较小采用DOM是可行的。
DOM解析就很常见了,我也就不多说了,但是这个方法是不建议使用的,因为他会加载整个xml文件,会消耗更多的系统资源。
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
/**
* 使用Dom解析xml文件
*
*/
public class DomXMLReader {
public static List<Person> readXML(InputStream inStream) {
List<Person> persons = new ArrayList<Person>();
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
try {
DocumentBuilder builder = factory.newDocumentBuilder();
Document dom = builder.parse(inStream);
Element root = dom.getDocumentElement();
NodeList items = root.getElementsByTagName("person");//查找所有person节点
for (int i = 0; i < items.getLength(); i++) {
Person person = new Person();
//得到第一个person节点
Element personNode = (Element) items.item(i);
//获取person节点的id属性值
person.setId(new Integer(personNode.getAttribute("id")));
//获取person节点下的所有子节点(标签之间的空白节点和name/age元素)
NodeList childsNodes = personNode.getChildNodes();
for (int j = 0; j < childsNodes.getLength(); j++) {
Node node = (Node) childsNodes.item(j); //判断是否为元素类型
if(node.getNodeType() == Node.ELEMENT_NODE){ Element childNode = (Element) node;
//判断是否name元素
if ("name".equals(childNode.getNodeName())) {
//获取name元素下Text节点,然后从Text节点获取数据 person.setName(childNode.getFirstChild().getNodeValue());
} else if (“age”.equals(childNode.getNodeName())) {
person.setAge(new Short(childNode.getFirstChild().getNodeValue()));
}
}
}
persons.add(person);
}
inStream.close();
} catch (Exception e) {
e.printStackTrace();
}
return persons;
}
pull:
除了可以使用 SAX和DOM解析XML文件,大家也可以使用Android内置的Pull解析器解析XML文件。 Pull解析器的运行方式与 SAX 解析器相似。它提供了类似的事件,如:开始元素和结束元素事件,使用parser.next()可以进入下一个元素并触发相应事件。事件将作为数值代码被 发送,因此可以使用一个switch对感兴趣的事件进行处理。当元素开始解析时,调用parser.nextText()方法可以获取下一个Text类型 元素的值。
既然android建议我们使用pull,我就详细的说一下这个方法吧。
创建android工程,配置可以单元测试的环境,然后考入我们上面的javabean和xml文件
创建service:
package cn.partner4java.pull.service;
import java.io.InputStream;
import java.util.List;
import cn.partner4java.pull.bean.Person;
public class PullService {
public static List<Person> readXML(InputStream inStream) {
return null;
}
}
接下啦我们就书写readXML方法:
//获取xmlpull,我们可以使用android给我们提供的一个简单获取类(便于快速获得pull解析器)
XmlPullParser parser = Xml.newPullParser();
//进行编码设置,把我们需要解析的内容给他
parser.setInput(inStream, "UTF-8");
//获取事件,就可以触发第一解析到的字符对应的事件
//他也是采用的流式触发
int enentType = parser.getEventType();
//这个方法是把流事件往后退
// parser.next();
List<Person> persons = null;
Person person = null;
//XmlPullParser.END_DOCUMENT:Logical end of the xml document. Returned from getEventType, next() and nextToken()
//when the end of the input document has been reached.
//就是判断这个事件不为文档末尾事件,我们就循环下去
while(enentType != XmlPullParser.END_DOCUMENT){
//对事件进行判断
switch (enentType) {
//是否为开始事件,可以进行数据初始化处理
case XmlPullParser.START_DOCUMENT:
persons = new ArrayList<Person>();
break;
//是否为开始元素事件,如<Persons>
//再往下,处理到<person id...也会触发这个事件
//也就是每解析一个字符就会触发这个事件
case XmlPullParser.START_TAG:
String tag = parser.getName();
//如果当前为person标签,我们需要获取id值
if("person".equals(tag)){
person = new Person();
person.setId(new Integer(parser.getAttributeValue(0)));
}else if(person != null){
if("name".equals(tag)){
//parser.nextText(),当前为name下一个节点,为文本
person.setName(parser.nextText());
}else if("age".equals(tag)){
person.setAge(new Short(parser.nextText()));
}
}
break;
case XmlPullParser.END_TAG://结束元素事件
//如果解析到person结束元素事件时,我们就把当前的person加到结合中
if (parser.getName().equalsIgnoreCase("person") && person != null) {
persons.add(person);
person = null;
}
}
enentType = parser.next();
}
return persons;
然后我们对读方法进行单元测试
package cn.partner4java.pull.junit;
import java.io.InputStream;
import java.util.List;
import cn.partner4java.pull.bean.Person;
import cn.partner4java.pull.service.PullService;
import android.test.AndroidTestCase;
public class PullServiceTest extends AndroidTestCase {
public void readXML() throws Exception{
InputStream inStream = this.getClass().getClassLoader().getResourceAsStream("person.xml");
List<Person> persons = PullService.readXML(inStream);
for(Person person:persons){
System.out.println(person.getId() + ":" + person.getName());
}
}
}
使用Pull解析器生成XML文件:
有些时候,我们需要生成一个XML文件,生成XML文件的方法有很多,如:可 以只使用一个StringBuilder组拼XML内容,然后把内容写入到文件中;或者使用DOM API生成XML文件,或者也可以使用pull解析器生成XML文件,这里推荐大家使用Pull解析器。
public static void writeXML(List<Person> persons, Writer writer) throws IllegalArgumentException, IllegalStateException, IOException{
XmlSerializer serializer = Xml.newSerializer();
serializer.setOutput(writer);
//开始文档
serializer.startDocument("UTF-8", true);
//设置开始标签
serializer.startTag(null, "persons");
for(Person person:persons){
serializer.startTag(null, "person");
serializer.attribute("", "id", String.valueOf(person.getId()));
serializer.startTag("", "name");
serializer.text(person.getName());
serializer.endTag("", "name");
serializer.startTag("", "age");
serializer.text(String.valueOf(person.getAge()));
serializer.endTag("", "age");
serializer.endTag(null, "person");
}
//结束标签
serializer.endTag(null, "persons");
//结束文档
serializer.endDocument();
}
大体上就是这个样子,是和xml文件相对象的,一个开始标签,一个结束标签。
单元测试:
public void writeXML() throws IllegalArgumentException, IllegalStateException, IOException{
List<Person> persons = new ArrayList<Person>();
persons.add(new Person(3, "王昌龙", (short)23));
persons.add(new Person(4, "王昌龙的小妾", (short)17));
File file = new File(Environment.getExternalStorageDirectory(),"person.xml");
FileOutputStream fileOutputStream = new FileOutputStream(file);
Writer writer = new OutputStreamWriter(fileOutputStream , "UTF-8");
PullService.writeXML(persons, writer);
writer.flush();
writer.close();
}
有一个问题,不知道你发现了没有,往SD卡写数据是要打开权限的,但是我确实没打开,也生成文件了。
<!-- 在SDCard中创建与删除文件权限 -->
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>
<!-- 往SDCard写入数据权限 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>