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イベントが連続して呼ばれるケースが見られ、その場合は空白が取得されてしまうので、コメント行にも書いたように空白データは対象外とする処理を挿入しておきました。

このやり方で、Android 2.3.xでもAndroid 4.2.xでも想定通りに動作しました。