XmlPullParserを使った処理が正常動作しない時の対処
RSSを読み込んでListViewに表示する処理を書いていたときのことです。
Android 2.3.xでは問題なくXMLをパース出来ていたのに、Android 4.2.xではうまく動作しませんでした。その問題に対してどのようにコードを修正したのか、その具体的な方法をここに記録として残したいと思います。
一般に、例えばニュース記事などのRSSを読み込んでXMLをパースする場合、以下の様な書き方が出来ると思います。ここでは簡便に、記事のタイトルとリンク先URLのみを取得するとします。
// 改善前のXMLパース処理 public MyListAdapter parseXml(InputStream is) throws IOException, XmlPullParserException { XmlPullParser parser = Xml.newPullParser(); try { parser.setInput(is, null); int event = parser.getEventType(); Item item = null; while (event != XmlPullParser.END_DOCUMENT) { String tag = null; switch (event) { case XmlPullParser.START_TAG: tag = parser.getName(); if (tag.equals("item")) { item = new Item(); } else if (item != null) { if (tag.equals("title")) { item.setTitle(parser.nextText()); // 問題の箇所 } else if (tag.equals("link")) { item.setLink(parser.nextText()); // 問題の箇所 } } break; case XmlPullParser.END_TAG: tag = parser.getName(); if (tag.equals("item")) { // ListViewに反映するためのArrayAdapterに追加 mAdapter.add(item); } break; } event = parser.next(); } } catch (Exception e) { e.printStackTrace(); } return mAdapter; }
// パースした結果を格納するためのクラス public class Item { private CharSequence mTitle; private CharSequence mLink; public Item(CharSequence title, CharSequence link) { mTitle = title; mLink = link; } public CharSequence getTitle() { return mTitle; } public void setTitle(CharSequence title) { mTitle = title; } public CharSequence getLink() { return mLink; } public void setLink(CharSequence link) { mLink = link; } }
ところがこの書き方の場合、冒頭で述べたようにAndroid 2.3.xでは正常に処理され、Android4.2.xではうまく動作しません。具体的なエラー箇所は、「parser.nextText()」のところです。ネット上で色々調べてみたところ、どうやらこのnextTextメソッド自体に不安定要因があるようです。具体的にはこの記事にて。
ではどのようにしてこの問題を回避したか。私は以下の様な書き方に変えました。
// 改善後のXMLパース処理 public MyListAdapter parseXml(InputStream is) throws IOException, XmlPullParserException { XmlPullParser parser = Xml.newPullParser(); try { parser.setInput(is, null); int event = parser.getEventType(); Item item = null; String tag = null; while (event != XmlPullParser.END_DOCUMENT) { switch (event) { case XmlPullParser.START_TAG: tag = parser.getName(); if (tag.equals("item")) { item = new Item(); } break; case XmlPullParser.TEXT: if (item != null) { String text = parser.getText(); // 空白データは対象外 if (text.trim().length() != 0) { if (tag.equals("title")) { item.setTitle(text); } else if (tag.equals("link")) { item.setLink(text); } } } break; case XmlPullParser.END_TAG: tag = parser.getName(); if (tag.equals("item")) { // ListViewに反映するためのArrayAdapterに追加 mAdapter.add(item); } break; } event = parser.next(); } } catch (Exception e) { e.printStackTrace(); } return mAdapter; }
つまり、開始タグであるSTART_TAGを読み込んだ後は必ずTEXTタグのイベントが発生するので、このTEXTタグのところでgetTextメソッドを使い、その内容(文字列)を取得します。そして、これら一連の動作をイベントがEND_DOCUMENTになるまでループ。
ただし、TEXTイベントが連続して呼ばれるケースが見られ、その場合は空白が取得されてしまうので、コメント行にも書いたように空白データは対象外とする処理を挿入しておきました。