Java 环境中,当您需要一个速度快的解析器并且希望最大限度减少应用程序的内存占用时,通常可以使用 SAX API。这非常适用于运行 Android 的移动设备。您可以在 Java 环境中照原样使用 SAX API,在 Android 上运行它不需要做任何修改。清单 5 显示了 FeedParser 接口的一个 SAX 实现。
清单 5. SAX 实现
public class SaxFeedParser extends BaseFeedParser {
protected SaxFeedParser(String feedUrl){
super(feedUrl);
}
public List<Message> parse() {
SAXParserFactory factory = SAXParserFactory.newInstance();
try {
SAXParser parser = factory.newSAXParser();
RssHandler handler = new RssHandler();
parser.parse(this.getInputStream(), handler);
return handler.getMessages();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
如果您以前使用过 SAX,那么这对您肯定非常熟悉。与任何 SAX 实现相同,大多数细节都在 SAX 处理程序中。在分解 XML 文档时,处理程序从 SAX 解析器接收事件。在本例中,您创建了一个新的名称为 RssHandler 的类,并将它注册为解析器的处理程序,如 清单 6 所示。
清单 6. SAX 处理程序
import static org.developerworks.android.BaseFeedParser.*;
public class RssHandler extends DefaultHandler{
private List<Message> messages;
private Message currentMessage;
private StringBuilder builder;
public List<Message> getMessages(){
return this.messages;
}
@Override
public void characters(char[] ch, int start, int length)
throws SAXException {
super.characters(ch, start, length);
builder.append(ch, start, length);
}
@Override
public void endElement(String uri, String localName, String name)
throws SAXException {
super.endElement(uri, localName, name);
if (this.currentMessage != null){
if (localName.equalsIgnoreCase(TITLE)){
currentMessage.setTitle(builder.toString());
} else if (localName.equalsIgnoreCase(LINK)){
currentMessage.setLink(builder.toString());
} else if (localName.equalsIgnoreCase(DESCRIPTION)){
currentMessage.setDescription(builder.toString());
} else if (localName.equalsIgnoreCase(PUB_DATE)){
currentMessage.setDate(builder.toString());
} else if (localName.equalsIgnoreCase(ITEM)){
messages.add(currentMessage);
}
builder.setLength(0);
}
}
@Override
public void startDocument() throws SAXException {
super.startDocument();
messages = new ArrayList<Message>();
builder = new StringBuilder();
}
@Override
public void startElement(String uri, String localName, String name,
Attributes attributes) throws SAXException {
super.startElement(uri, localName, name, attributes);
if (localName.equalsIgnoreCase(ITEM)){
this.currentMessage = new Message();
}
}
}
RssHandler 类扩展了 org.xml.sax.helpers.DefaultHandler 类。该类为 SAX 解析器生成的事件所对应的所有方法都提供了一个默认的非操作实现。这允许子类根据需要仅覆盖一些方法。RssHandler 提供了一个额外的 API,即 getMessages。它返回处理程序在从 SAX 解析器接收事件时所收集的 Message 对象列表。它有另外两个内部变量,currentMessage 针对被解析的 Message 实例,以及名称为 builder 的 StringBuilder 变量,用于存储文本节点中的字符数据。解析器将相应事件发送给处理程序时会调用 startDocument 方法,这两个变量的初始化操作就是在此时完成。
查看 清单 6 中的 startElement 方法。在 XML 文档中每次遇到开始标记时都会调用它。您只关心该标记何时为 ITEM 标记。对于这种情况,您将创建一个新的 Message。现在来看 characters 方法。遇到文本节点中的字符数据时便会调用此方法。数据只是被添加到 builder 变量中。最后,我们来看 endElement 方法。遇到结束标记时会调用此方法。对于与某 Message 属性相对应的标记,如 TITLE 和 LINK,则使用 builder 变量中的数据在 currentMessage 上设置适当的属性。如果结束标记是一个 ITEM,则 currentMessage 将被添加到 Messages 列表中。所有这些都是非常典型的 SAX 解析;此处的一切都不是 Android 所特有的。因此,如果您知道如何编写 Java SAX 解析器,则应该知道如何编写 Android SAX 解析器。但是,Android SDK 确实在 SAX 上添加了一些便捷的特性。
回页首
更加简单的 SAX 解析
Android SDK 提供了一个名称为 android.util.Xml 的实用类。清单 7 展示了如何使用这个相同的实用类来设置一个 SAX 解析器。
清单 7. Android SAX 解析器
public class AndroidSaxFeedParser extends BaseFeedParser {
public AndroidSaxFeedParser(String feedUrl) {
super(feedUrl);
}
public List<Message> parse() {
RssHandler handler = new RssHandler();
try {
Xml.parse(this.getInputStream(), Xml.Encoding.UTF_8, handler);
} catch (Exception e) {
throw new RuntimeException(e);
}
return handler.getMessages();
}
}
注意,这个类仍然使用了一个标准的 SAX 处理程序,因此您仅仅重用了 清单 7 中所示的 RssHandler。能够重用 SAX 处理程序是非常不错的,但其代码稍微有些复杂。您可以想像,如果需要解析一个更加复杂的 XML 文档,则处理程序可能会带来各种各样的 bug。举例来说,回头看看 清单 6 中的 endElement 方法。注意,在尝试设置属性之前,它检查了 currentMessage 是否为 null。现在,再回头看看 清单 4 中的示例 XML。注意,ITEM 标记外部有一些 TITLE 和 LINK 标记。这就是使用 null 检查的原因。否则,每一个 TITLE 标记会导致一个 NullPointerException。Android 提供了自己独有的 SAX API(参见 清单 8),它排除了您编写自己的 SAX 处理程序的需要。
清单 8. 经过简化的 Android SAX 解析器
public class AndroidSaxFeedParser extends BaseFeedParser {
public AndroidSaxFeedParser(String feedUrl) {
super(feedUrl);
}
public List<Message> parse() {
final Message currentMessage = new Message();
RootElement root = new RootElement("rss");
final List<Message> messages = new ArrayList<Message>();
Element channel = root.getChild("channel");
Element item = channel.getChild(ITEM);
item.setEndElementListener(new EndElementListener(){
public void end() {
messages.add(currentMessage.copy());
}
});
item.getChild(TITLE).setEndTextElementListener(new EndTextElementListener(){
public void end(String body) {
currentMessage.setTitle(body);
}
});
item.getChild(LINK).setEndTextElementListener(new EndTextElementListener(){
public void end(String body) {
currentMessage.setLink(body);
}
});
item.getChild(DESCRIPTION).setEndTextElementListener(new
EndTextElementListener(){
public void end(String body) {
currentMessage.setDescription(body);
}
});
item.getChild(PUB_DATE).setEndTextElementListener(new EndTextElementListener(){
public void end(String body) {
currentMessage.setDate(body);
}
});
try {
Xml.parse(this.getInputStream(), Xml.Encoding.UTF_8,
root.getContentHandler());
} catch (Exception e) {
throw new RuntimeException(e);
}
return messages;
}
}
新的 SAX 解析代码并未使用 SAX 处理程序,而是使用了 SDK 中的 android.sax 包中的类。这些类允许您构建 XML 文档的结构,并根据需要添加事件监听程序。在以上代码中,您声明文档将有一个 rss 根元素,并且它有一个 channel 子元素。然后,您声明 channel 将有一个 ITEM 子元素,并且开始添加监听程序。对于每个监听程序,您都使用了一个实现了特定接口(EndElementListner 或 EndTextElementListener)的匿名内部类。注意,您不需要跟踪字符数据。不仅仅因为这样会更加简单,更重要的是更加高效。最后,在调用 Xml.parse 实用方法时,您将传递一个通过根元素生成的处理程序。
清单 8 中的所有代码都是可选的。如果您习惯 Java 环境中的标准 SAX 解析代码,那么您可以坚持使用它。如果您希望尝试 Android SDK 所提供的便捷的包装器,那么也可以使用它。如果您完全不希望使用 SAX 会怎样呢?可以使用一些备选方案。其中的首选方法就是 DOM。