JSON虽然已经成为移动端网络交互的主流格式,但在对接老旧后端系统、解析RSS订阅源、读取SVG资源或处理SOAP接口时,XML依然随处可见。Flutter官方SDK本身并没有提供XML解析能力,好消息是Dart生态中有一个成熟稳定的xml包,它能覆盖绝大多数XML处理需求。本文将从安装配置、文档模型解析、事件流解析三个层面,完整讲解Dart与Flutter中处理XML数据的方法。

一、安装与引入xml包
xml包是Pure Dart实现,不依赖任何平台原生代码,因此在Android、iOS、Web、桌面端都能直接使用。首先在项目的pubspec.yaml中添加依赖,建议使用^6.x版本:
dependencies: xml: ^6.5.0
保存后执行flutter pub get,或在IDE中点击自动拉取。接着在需要解析的Dart文件中导入:
import 'package:xml/xml.dart';
需要说明的是,xml 6.x版本对API做了较大调整,废弃了早期版本中parse()顶层函数,统一改为XmlDocument.parse()静态方法。如果你在网上看到老教程使用Xml.parse(str)的写法,在6.x版本中会直接报错,这一点新手尤其容易踩坑。
二、文档模型解析:XmlDocument的常规用法
文档模型解析是最直观的方式:把整个XML字符串一次性读入内存,构建出一棵可任意遍历的节点树,然后按需读取节点名称、属性和文本内容。对于几KB到几百KB的常规配置文件或接口返回,这种方式开发效率最高。
假设有一段RSS格式的XML数据,我们想把每条新闻的标题、链接和发布时间取出来:
import 'package:xml/xml.dart';
void main() {
const xmlString = '''
<rss>
<channel>
<item>
<title>Flutter 3.0 发布</title>
<link>https://ipipp.com/news/1</link>
<pubDate>Mon, 10:00:00</pubDate>
</item>
<item>
<title>Dart 3 新特性解读</title>
<link>https://ipipp.com/news/2</link>
<pubDate>Tue, 09:30:00</pubDate>
</item>
</channel>
</rss>
''';
final document = XmlDocument.parse(xmlString);
// 查找所有item节点,无论嵌套层级多深
final items = document.findAllElements('item');
for (final item in items) {
final title = item.getElement('title')?.innerText ?? '';
final link = item.getElement('link')?.innerText ?? '';
final pubDate = item.getElement('pubDate')?.innerText ?? '';
print('标题: $title, 链接: $link, 时间: $pubDate');
}
}
代码中有几个关键API值得展开说明。findAllElements会递归搜索整棵树中所有匹配名称的元素,适合不关心层级位置的场景;如果只想找直接子节点,则应使用getElement或findElements,二者只会在下一级子节点中查找,避免误取深层嵌套的同名节点。
读取属性值同样简单。假设节点写法是<item id="1001" hot="true">,可以通过element.getAttribute('id')获取属性,返回值是可空字符串,建议配合空安全操作符处理。而innerText返回节点包裹的所有文本(含子节点文本),text则只返回直接文本,二者在存在嵌套时结果不同,解析混合内容时要留意区分。
xml包还支持类似XPath的简写查找语法,例如document.findAllElements('channel > item')表示查找channel的直接子节点item,路径中支持>和@属性名等写法。它并不是完整的XPath实现,但对付常见的取数需求已经够用。
三、结合http包请求并解析远程XML接口
实际业务中XML往往来自网络请求。下面的例子演示了用http包请求一个RSS地址,拿到响应后交给xml包解析,并把结果转成Dart模型对象的完整流程。这也是Flutter应用中最常见的组合用法。
import 'package:http/http.dart' as http;
import 'package:xml/xml.dart';
class NewsItem {
final String title;
final String link;
NewsItem(this.title, this.link);
factory NewsItem.fromXmlElement(XmlElement element) {
return NewsItem(
element.getElement('title')?.innerText ?? '无标题',
element.getElement('link')?.innerText ?? '',
);
}
}
Future<List<NewsItem>> fetchNews(String url) async {
final response = await http.get(Uri.parse(url));
if (response.statusCode != 200) {
throw Exception('请求失败: ${response.statusCode}');
}
final document = XmlDocument.parse(response.body);
return document
.findAllElements('item')
.map(NewsItem.fromXmlElement)
.toList();
}
把解析逻辑封装进NewsItem.fromXmlElement工厂构造函数是个值得推荐的习惯:界面层拿到的是类型安全的模型列表,解析细节被完全隔离,XML结构调整时只需要改一处代码。另外注意http默认遵循重定向,但某些老接口可能返回GBK编码,此时response.body会乱码,应改用response.bodyBytes配合utf8.decode或gbkCodec手动解码后再交给XmlDocument解析。
四、大文件场景:事件流解析
文档模型把整棵树加载进内存,解析一个10MB的XML大约要占用数倍的内存空间,在低端手机上处理超大文件可能引发卡顿甚至OOM。针对这种场景,xml包提供了基于事件流的解析方式,类似传统SAX模型,数据边读边处理,内存占用恒定。
import 'package:xml/xml_events.dart';
void main() {
const xmlString = '<root><item>A</item><item>B</item></root>';
parseEvents(xmlString).forEachEvent((event) {
if (event is XmlStartElementEvent && event.name == 'item') {
print('开始节点: ${event.name}');
}
if (event is XmlTextEvent) {
print('文本内容: ${event.text}');
}
});
}
事件流模式下,解析器不会构建节点树,而是顺序抛出开始标签、文本、结束标签等事件,由回调自行决定保留什么、丢弃什么。它的代价是代码编写更繁琐,需要自己维护上下文状态(比如记录当前处于哪个父节点之下),所以只建议在文件体积大、或数据可流式处理(如边下载边解析)时使用。日常几十KB以内的数据,老老实实用XmlDocument反而更省事。
五、方案对比与选择建议
三种方式各有定位,简单总结如下:
| 方案 | 内存占用 | 开发难度 | 适用场景 |
|---|---|---|---|
| XmlDocument文档模型 | 较高 | 低 | 常规接口返回、配置文件、RSS解析 |
| XmlEvent事件流 | 恒定且低 | 中高 | 大文件、流式下载解析 |
| 封装模型层 | 取决于底层 | 略增 | 任何需要长期维护的项目 |
选型的基本原则很清晰:数据量小、结构需要反复随机访问就用文档模型;数据量大或可以分块处理就上事件流;无论哪种底层方式,都建议在业务层封装成模型类,让解析代码不散落在界面里。此外,解析外部来源的XML时要做好异常捕获,XmlDocument.parse遇到非法字符或标签未闭合会直接抛出XmlException,用try-catch包一层并给出兜底提示,是保证应用稳定性的必要手段。掌握这些要点后,Dart处理XML就不再是难题了。
Dart解析XMLFlutter XML解析xml包使用修改时间:2026-09-12 01:24:40