1

我正在为我的 android 应用程序开发一项新功能,以启用数据备份和恢复。我正在使用 XML 文件来备份数据。这是为输出文件设置编码的一段代码:

XmlSerializer serializer = Xml.newSerializer();
FileWriter fileWriter = new FileWriter(file, false);
serializer.setOutput(fileWriter);
serializer.startDocument("UTF-8", true);
[... Write data to the file....]

这就是我尝试从 XML 文件导入数据的方式。首先,我检查编码是否正确:

XmlPullParser parser = Xml.newPullParser();
FileReader reader = new FileReader(file);
parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false);
parser.setInput(reader);
if(!"UTF-8".equals(parser.getInputEncoding())) {
    throw new IOException("Incorrect file encoding");
}
[... Read data from the file....]

在这里我遇到了一个问题。此代码在 Android 2.3.3(设备和模拟器)上运行良好,编码被正确检测为“UTF-8”。但是在 API11+ 版本(Honeycomb、ICS、JB)上会抛出异常。当我在调试模式下运行它时,我可以看到 parser.getInputEncoding() 返回null。我检查了在 2.3.3 及更高版本上生成的实际 XML 文件,它们具有完全相同的标题:<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>. 为什么 getInputEncoding() 在 API11+ 上返回 null?

其他发现:

我发现有一种方法可以正确检测 API11+ 设备上的文件编码,FileInputStream而不是FileReader像这样:

XmlPullParser parser = Xml.newPullParser();
FileInputStream stream = new FileInputStream(file);
parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false);
parser.setInput(stream, null);
if(!"UTF-8".equals(parser.getInputEncoding())) {
    throw new IOException("Incorrect file encoding");
}
[... Read data from the file....]

在这种情况下,getInputEncoding() 可以正确检测 API11+ 模拟器和设备上的 UTF-8 编码,但它在 2.3.3 上返回 null。所以现在我可以在代码中插入一个 fork 来使用 API11+ 上的 FileReader 和 pre-API11 上的 FileInputStream:

if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
    parser.setInput(stream, null);
} else {
    parser.setInput(reader);
}

但是使用 XmlPullParser.getInputEncoding() 检查编码的正确方法是什么?为什么不同版本的 Android 行为会有所不同,具体取决于我使用哪一个:FileInputStream 还是 FileReader?

4

2 回答 2

5

经过更多的反复试验,我终于设法弄清楚发生了什么。因此,尽管文档说:

从历史上看,Android 有此接口的两个实现:通过 XmlPullParserFactory.newPullParser() 实现的 KXmlParser。ExpatPullParser,通过 Xml.newPullParser()。

任何一个选择都很好。本节中的示例通过 Xml.newPullParser() 使用 ExpatPullParser。

现实情况是,在较旧的 API(例如 2.3.3 )上Xml.newPullParser()返回ExpatPullParser对象。在 Ice Cream Sandwich 及以上时,它会返回KXmlParser对象。正如我们从这篇博文中看到的那样,Android 开发人员从 2011 年 12 月就知道这一点:

在 Ice Cream Sandwich 中,我们将 Xml.newPullParser() 更改为返回 KxmlParser 并删除了 ExpatPullParser 类。

...但从不费心更新官方文档。

那么如何KXmlParser在 Ice Cream Sandwich 之前检索 API 上的对象呢?简单的:

XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
XmlPullParser parser = factory.newPullParser();

...事实上,这适用于所有版本的 android,新旧。然后为解析器的 setInput() 方法提供一个 FileInputStream ,保留默认编码null

FileInputStream stream = null;
stream = new FileInputStream(file);
parser.setInput(stream, null);

此后,在 API 11 及更高版本上,您可以立即调用 parser.getInputEncoding() ,它将返回正确的编码。但在 API11 之前的版本中,除非您首先调用 parser.next(),否则它将返回 null,正如 @Esailija 在他的回答中正确指出的那样。有趣的是,在 API11+ 上调用 next() 不会产生任何负面影响,因此您可以在所有版本上安全地使用此代码:

parser.next();
String encoding = parser.getInputEncoding();

这将正确返回“UTF-8”。

于 2013-04-23T23:16:10.733 回答
0

FileReader和其他阅读器不检测编码。他们只是使用平台默认编码,巧合的是 UTF-8。它与文件的实际编码无关。

encoding在您阅读到足以看到属性之前,您无法检测 XML 文件编码。

getInputEncoding()文档

如果 inputEncoding 为 null并且解析器支持编码检测功能,它必须返回检测到的编码

和:

如果调用了 setInput(Reader),则返回 null。

因此,pre 11 似乎不支持使用setInput(is, null). 我不知道您"UTF-8"在使用setInput(reader)文档时会得到怎样的结果,因为它应该返回null

然后:

如果存在 XML 声明,则在第一次调用 next 之后,此方法将返回声明的编码。

所以在 11 之前,你可以尝试在调用.next()之前先调用.getInputEncoding

于 2013-04-18T15:50:15.820 回答