因为太困了,就没事整理一下笔记,希望对大家有所帮助

因为在开发中,我们会常用到xml文件的读写操作,因为手机操作有很多版本问题和很多小数据在数据库不是良好的解决方案。所以会用到很多xml操作。


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"/>