2011年12月20日 星期二

小心 Android Bug!! - XmlPullParser.nextText()


使用 XmlPullParser 在 Android 上來解析 XML 文件是高效能且易維護的。由於歷史原因,XML 文件解析在 Android 系統上有兩種實現:
  • KXmlParser: 通過 XmlPullParserFactory.newPullParser() 函數獲取
  • ExpatPullParser: 通過 Xml.newPullParser() 函數獲取
但 Xml.newPullParser() 的實現有個 bug,當調用函數 nextText() 後並不一定像文檔描述的那樣總是前進到 END_TAG。一些程序可能使用再次調用 next() 或者 nextTag() 函數來解決這個問題。

public void parseXml(Reader reader) throws XmlPullParserException, IOException {

    XmlPullParser parser = Xml.newPullParser(); parser.setInput(reader);
    parser.nextTag(); parser.require(XmlPullParser.START_TAG, null, "menu");
    while (parser.nextTag() == XmlPullParser.START_TAG)
    {
        parser.require(XmlPullParser.START_TAG, null, "item"); String itemText = parser.nextText(); parser.nextTag(); // this call shouldn’t be necessary!
        parser.require(XmlPullParser.END_TAG, null, "item");
        System.out.println("menu option: " + itemText);
    }
    parser.require(XmlPullParser.END_TAG, null, "menu");
}

在 Android 4.0 版本中,Android 的團隊修改了 Xml.newPullParser() 函數的實現,返回 KxmlParser 並且刪除了 ExpatPullParser 類,這樣修復了nextTag() bug。不幸的是那些按照上面的代碼修改該錯誤的程序在 4.0 系統上運行將會崩潰。

org.xmlpull.v1.XmlPullParserException: expected: END_TAG {null}item (position:START_TAG <item>@1:37 in java.io.StringReader@40442fa8)
     at org.kxml2.io.KXmlParser.require(KXmlParser.java:2046)
     at com.publicobject.waffles.Menu.parseXml(Menu.java:25)
 at com.publicobject.waffles.Menu.main(Menu.java:32)

可以按照如下的方式來修復該問題,調用函數 nextText() 後只有當前位置不是 END_TAG 的時候才調用 nextTag()。

while (parser.nextTag() == XmlPullParser.START_TAG) {
    parser.require(XmlPullParser.START_TAG, null, "item");
    String itemText = parser.nextText();
    if (parser.getEventType() != XmlPullParser.END_TAG) {
        parser.nextTag();
    }
    parser.require(XmlPullParser.END_TAG, null, "item");
    System.out.println("menu option: " + itemText);
}

上面的代碼在所有的 Andr​​oid 版本上都能正常運行。如果您的程序使用了 nextText() 函數,請使用下面的幫助函數來替代所有的 nextText() 函數調用:

private String safeNextText(XmlPullParser parser)
        throws XmlPullParserException, IOException {
    String result = parser.nextText();
    if (parser.getEventType() != XmlPullParser.END_TAG) {
        parser.nextTag();
    }
    return result;
}

現在只使用一個 XmlPullParser 實現,可以讓維護系統更簡單,並且可以讓 Android 開發團隊集中精力在提高系統性能上面。

沒有留言:

張貼留言