Различное поведение XmlPullParser.getInputEncoding() в версиях Android с API11+ и до API11.

Я разрабатываю новую функцию для своего приложения для 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. Я проверил фактические файлы XML, созданные в версии 2.3.3 и более поздних, и они имеют точно такие же заголовки: <?xml version='1.0' encoding='UTF-8' standalone='yes' ?>. Почему getInputEncoding() возвращает null в API11+?

Дополнительные выводы:

Я обнаружил, что есть способ правильно определить кодировку файлов на устройствах 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() правильно определяет кодировку UTF-8 на эмуляторах и устройствах API11+, но возвращает null на 2.3.3. Итак, на данный момент я могу вставить ответвление в код, чтобы использовать FileReader в API11+ и FileInputStream в pre-API11:

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

Но как правильно проверить кодировку с помощью XmlPullParser.getInputEncoding()? Почему разные версии Android ведут себя по-разному в зависимости от того, какую из них я использую: FileInputStream или FileReader?


person Anton Cherkashyn    schedule 15.04.2013    source источник


Ответы (2)


После еще нескольких проб и ошибок мне наконец удалось понять, что происходит. Итак, несмотря на то, что в документации сказано:

Исторически в Android было две реализации этого интерфейса: KXmlParser через XmlPullParserFactory.newPullParser(). ExpatPullParser через Xml.newPullParser().

Любой выбор в порядке. Пример в этом разделе использует ExpatPullParser через Xml.newPullParser().

Реальность такова, что в более старых API, таких как 2.3.3, Xml.newPullParser() возвращает ExpatPullParser объект. В Ice Cream Sandwich и выше он возвращает KXmlParser объект. Как видно из этой записи в блоге , разработчики андроида знали об этом с декабря 2011 года:

В Ice Cream Sandwich мы изменили Xml.newPullParser(), чтобы он возвращал KxmlParser, и удалили наш класс ExpatPullParser.

... но никогда не удосужился обновить официальную документацию.

Так как же получить объект KXmlParser в API до Ice Cream Sandwich? Простой:

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

... на самом деле это работает на всех версиях Android, новых и старых. Затем вы предоставляете FileInputStream методу setInput() вашего синтаксического анализатора, оставляя кодировку по умолчанию null:

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

После этого в API 11 и выше вы можете сразу вызвать parser.getInputEncoding(), и он вернет правильную кодировку. Но в версиях до API11 он вернет null, если вы сначала не вызовете parser.next(), как правильно указал @Esailija в своем ответе. Интересно, что в API11+ вызов next() не имеет никакого негативного эффекта, поэтому вы можете смело использовать этот код во всех версиях:

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

И это правильно вернет «UTF-8».

person Anton Cherkashyn    schedule 23.04.2013

FileReader и другие программы чтения не определяют кодировку. Они просто используют кодировку платформы по умолчанию, которая по совпадению может быть UTF-8. Это не имеет никакого отношения к фактической кодировке файла.

Вы не сможете определить кодировку файла XML, пока не прочтете его достаточно, чтобы увидеть атрибут encoding.

Из getInputEncoding() документации.

если inputEncoding имеет значение null, и синтаксический анализатор поддерживает функцию определения кодировки, он должен вернуть обнаруженную кодировку

И:

Если был вызван setInput(Reader), возвращается null.

Таким образом, оказывается, что pre 11 не поддерживает обнаружение, которое включается с помощью setInput(is, null). Я не знаю, как вы получаете "UTF-8" при использовании setInput(reader), поскольку в документации говорится, что он должен возвращать null.

Затем:

После первого вызова next, если присутствует объявление XML, этот метод вернет объявленную кодировку.

Таким образом, в pre 11 вы могли попробовать сначала вызвать .next(), прежде чем вызывать .getInputEncoding.

person Esailija    schedule 18.04.2013
comment
Я пытался следовать вашим инструкциям, но вызов .next() в pre-API11 не помогает, getInputEncoding() продолжает возвращать null. - person Anton Cherkashyn; 22.04.2013
comment
Пожалуйста, ознакомьтесь с моим ответом на вопрос. Вы были правы, назвав .next(), но был и второй момент: мне следовало использовать XmlPullParserFactory.newPullParser() вместо Xml.newPullParser(). И, конечно же, используйте FileInputStream вместо FileReader. - person Anton Cherkashyn; 24.04.2013