【Android Developers Training】 81. 解析XML

系统 1715 0

注:本文翻译自Google官方的Android Developers Training文档,译者技术一般,由于喜爱安卓而产生了翻译的念头,纯属个人兴趣爱好。

原文链接: http://developer.android.com/training/basics/network-ops/xml.html


可扩展标记语言(XML)是一种将文档编码为机器可阅读的形式的规则集合。XML是一种在互联网中分享数据的比较流行的格式。那些频繁更新内容的网站(如新的站点或者博客),经常会提供一个XML源,这样外部程序就可以与内容变更保持同步。上传及解析XML数据对于需要联网的应用来说是一个很平常的任务。这节课将讲解如何解析XML文档并使用它们的数据。


一). 选择一个解析器

我们推荐使用 XmlPullParser ,它是一个在Android上解析XML的一种比较有效及稳定的方法。历史中Android有两种实现该接口的方法:

每一种选择都是可以的。不过这里我们使用第二个例子。  


二). 分析源

解析源的第一步是决定哪些字段是你感兴趣的。解析器会提取这些你感兴趣的字段数据并把其余的忽略。

下面是在应用中被解析的源的一段摘录。每一个到 StackOverflow.com 的推送都会在源中显示为一个 entry 标签,并 包含若干 entry 子标签:

      
        <?
      
      
        xml version="1.0" encoding="utf-8"
      
      
        ?>
      
      
        <
      
      
        feed 
      
      
        xmlns
      
      
        ="http://www.w3.org/2005/Atom"
      
      
         xmlns:creativeCommons
      
      
        ="http://backend.userland.com/creativeCommonsRssModule"
      
      
         ..."
      
      
        >
      
      
        <
      
      
        title 
      
      
        type
      
      
        ="text"
      
      
        >
      
      newest questions tagged android - Stack Overflow
      
        </
      
      
        title
      
      
        >
      
      
        

...

    
      
      
        <
      
      
        entry
      
      
        >
      
      
        

    ...

    
      
      
        </
      
      
        entry
      
      
        >
      
      
        <
      
      
        entry
      
      
        >
      
      
        <
      
      
        id
      
      
        >
      
      http://stackoverflow.com/q/9439999
      
        </
      
      
        id
      
      
        >
      
      
        <
      
      
        re:rank 
      
      
        scheme
      
      
        ="http://stackoverflow.com"
      
      
        >
      
      0
      
        </
      
      
        re:rank
      
      
        >
      
      
        <
      
      
        title 
      
      
        type
      
      
        ="text"
      
      
        >
      
      Where is my data file?
      
        </
      
      
        title
      
      
        >
      
      
        <
      
      
        category 
      
      
        scheme
      
      
        ="http://stackoverflow.com/feeds/tag?tagnames=android&sort=newest/tags"
      
      
         term
      
      
        ="android"
      
      
        />
      
      
        <
      
      
        category 
      
      
        scheme
      
      
        ="http://stackoverflow.com/feeds/tag?tagnames=android&sort=newest/tags"
      
      
         term
      
      
        ="file"
      
      
        />
      
      
        <
      
      
        author
      
      
        >
      
      
        <
      
      
        name
      
      
        >
      
      cliff2310
      
        </
      
      
        name
      
      
        >
      
      
        <
      
      
        uri
      
      
        >
      
      http://stackoverflow.com/users/1128925
      
        </
      
      
        uri
      
      
        >
      
      
        </
      
      
        author
      
      
        >
      
      
        <
      
      
        link 
      
      
        rel
      
      
        ="alternate"
      
      
         href
      
      
        ="http://stackoverflow.com/questions/9439999/where-is-my-data-file"
      
      
        />
      
      
        <
      
      
        published
      
      
        >
      
      2012-02-25T00:30:54Z
      
        </
      
      
        published
      
      
        >
      
      
        <
      
      
        updated
      
      
        >
      
      2012-02-25T00:30:54Z
      
        </
      
      
        updated
      
      
        >
      
      
        <
      
      
        summary 
      
      
        type
      
      
        ="html"
      
      
        >
      
      
        <
      
      
        p
      
      
        >
      
      I have an Application that requires a data file...
      
        </
      
      
        p
      
      
        >
      
      
        </
      
      
        summary
      
      
        >
      
      
        </
      
      
        entry
      
      
        >
      
      
        <
      
      
        entry
      
      
        >
      
      
        

    ...

    
      
      
        </
      
      
        entry
      
      
        >
      
      
        

...


      
      
        </
      
      
        feed
      
      
        >
      
    

应用会提取会提取 entry 标签及其子标签: title link summary 子标签的数据。


三). 初始化解析器

下一步是初始化解析器,并启动解析的步骤。在下面的代码片段中,一个不处理命名空间的解析器被初始化,并使用 InputStream 作为参数。通过调用 nextTag() 开始解析的步骤,并激活 readFeed() 方法,该方法提取并处理应用感兴趣的数据:

      
        public
      
      
        class
      
      
         StackOverflowXmlParser {

    
      
      
        //
      
      
         We don't use namespaces
      
      
        private
      
      
        static
      
      
        final
      
       String ns = 
      
        null
      
      
        ;

   

    
      
      
        public
      
       List parse(InputStream in) 
      
        throws
      
      
         XmlPullParserException, IOException {

        
      
      
        try
      
      
         {

            XmlPullParser parser 
      
      =
      
         Xml.newPullParser();

            parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, 
      
      
        false
      
      
        );

            parser.setInput(in, 
      
      
        null
      
      
        );

            parser.nextTag();

            
      
      
        return
      
      
         readFeed(parser);

        } 
      
      
        finally
      
      
         {

            in.close();

        }

    }

 ... 

}
      
    

四). 阅读源

readFeed() 方法执行一些工作来处理源。它寻找 entry 标签作为开始递归处理的起始点。如果一个标签不是 entry 标签,那么就忽略它。一点整个源都被递归处理完了, readFeed() 方法返回一个包含它从源中提取的字段的 List (包含子数据成员)。该 List 被解析器返回。

      
        private
      
       List readFeed(XmlPullParser parser) 
      
        throws
      
      
         XmlPullParserException, IOException {

    List entries 
      
      = 
      
        new
      
      
         ArrayList();



    parser.require(XmlPullParser.START_TAG, ns, 
      
      "feed"
      
        );

    
      
      
        while
      
       (parser.next() !=
      
         XmlPullParser.END_TAG) {

        
      
      
        if
      
       (parser.getEventType() !=
      
         XmlPullParser.START_TAG) {

            
      
      
        continue
      
      
        ;

        }

        String name 
      
      =
      
         parser.getName();

        
      
      
        //
      
      
         Starts by looking for the entry tag
      
      
        if
      
       (name.equals("entry"
      
        )) {

            entries.add(readEntry(parser));

        } 
      
      
        else
      
      
         {

            skip(parser);

        }

    }  

    
      
      
        return
      
      
         entries;

}
      
    

五). 解析XML

解析一个XML源的步骤如下:

  1. 如第二节中所述,在你的应用中标识出你希望包含的标签。该例子中提取的数据为 entry 标签及其子标签: title link summary 子标签的数据。
  2. 创建下列方法:
    • 为每个你感兴趣的标签创建“ read ”方法。例如, readEntry(), readTitle()等。解析器从输入流中读取标签。当它遇到了名为 entry, title, link或 summary时,它会为标签调用相应的方法。否则就略过该标签。
    • 为每个不同类型标签提取数据并将解析器推进到下一个标签的方法。例如:
      • 对于 title和 summary标签,解析器调用 readText()。该方法提取通过调用 parser.getText(),从这些标签中提取数据。
      • 对于 link标签,解析器首先确定该link是否是自己感兴趣的,如果是的话就调用 parser.getAttributeValue()来提取它的值。
      • 对于 entry标签,解析器会调用 readEntry()。该方法解析entry中的子标签,并返回一个 Entry 对象,其中包含了数据成员: title, link和 summary。
    • 一个用以辅助的方法 skip()。更多信息可以阅读: Skip Tags You Don't Care About

下列代码片段展示了如何解析上述标签:

      
        public
      
      
        static
      
      
        class
      
      
         Entry {

    
      
      
        public
      
      
        final
      
      
         String title;

    
      
      
        public
      
      
        final
      
      
         String link;

    
      
      
        public
      
      
        final
      
      
         String summary;



    
      
      
        private
      
      
         Entry(String title, String summary, String link) {

        
      
      
        this
      
      .title =
      
         title;

        
      
      
        this
      
      .summary =
      
         summary;

        
      
      
        this
      
      .link =
      
         link;

    }

}

  


      
      
        //
      
      
         Parses the contents of an entry. If it encounters a title, summary, or link tag, hands them off


      
      
        //
      
      
         to their respective "read" methods for processing. Otherwise, skips the tag.
      
      
        private
      
       Entry readEntry(XmlPullParser parser) 
      
        throws
      
      
         XmlPullParserException, IOException {

    parser.require(XmlPullParser.START_TAG, ns, 
      
      "entry"
      
        );

    String title 
      
      = 
      
        null
      
      
        ;

    String summary 
      
      = 
      
        null
      
      
        ;

    String link 
      
      = 
      
        null
      
      
        ;

    
      
      
        while
      
       (parser.next() !=
      
         XmlPullParser.END_TAG) {

        
      
      
        if
      
       (parser.getEventType() !=
      
         XmlPullParser.START_TAG) {

            
      
      
        continue
      
      
        ;

        }

        String name 
      
      =
      
         parser.getName();

        
      
      
        if
      
       (name.equals("title"
      
        )) {

            title 
      
      =
      
         readTitle(parser);

        } 
      
      
        else
      
      
        if
      
       (name.equals("summary"
      
        )) {

            summary 
      
      =
      
         readSummary(parser);

        } 
      
      
        else
      
      
        if
      
       (name.equals("link"
      
        )) {

            link 
      
      =
      
         readLink(parser);

        } 
      
      
        else
      
      
         {

            skip(parser);

        }

    }

    
      
      
        return
      
      
        new
      
      
         Entry(title, summary, link);

}




      
      
        //
      
      
         Processes title tags in the feed.
      
      
        private
      
       String readTitle(XmlPullParser parser) 
      
        throws
      
      
         IOException, XmlPullParserException {

    parser.require(XmlPullParser.START_TAG, ns, 
      
      "title"
      
        );

    String title 
      
      =
      
         readText(parser);

    parser.require(XmlPullParser.END_TAG, ns, 
      
      "title"
      
        );

    
      
      
        return
      
      
         title;

}

  


      
      
        //
      
      
         Processes link tags in the feed.
      
      
        private
      
       String readLink(XmlPullParser parser) 
      
        throws
      
      
         IOException, XmlPullParserException {

    String link 
      
      = ""
      
        ;

    parser.require(XmlPullParser.START_TAG, ns, 
      
      "link"
      
        );

    String tag 
      
      =
      
         parser.getName();

    String relType 
      
      = parser.getAttributeValue(
      
        null
      
      , "rel"
      
        );  

    
      
      
        if
      
       (tag.equals("link"
      
        )) {

        
      
      
        if
      
       (relType.equals("alternate"
      
        )){

            link 
      
      = parser.getAttributeValue(
      
        null
      
      , "href"
      
        );

            parser.nextTag();

        } 

    }

    parser.require(XmlPullParser.END_TAG, ns, 
      
      "link"
      
        );

    
      
      
        return
      
      
         link;

}




      
      
        //
      
      
         Processes summary tags in the feed.
      
      
        private
      
       String readSummary(XmlPullParser parser) 
      
        throws
      
      
         IOException, XmlPullParserException {

    parser.require(XmlPullParser.START_TAG, ns, 
      
      "summary"
      
        );

    String summary 
      
      =
      
         readText(parser);

    parser.require(XmlPullParser.END_TAG, ns, 
      
      "summary"
      
        );

    
      
      
        return
      
      
         summary;

}




      
      
        //
      
      
         For the tags title and summary, extracts their text values.
      
      
        private
      
       String readText(XmlPullParser parser) 
      
        throws
      
      
         IOException, XmlPullParserException {

    String result 
      
      = ""
      
        ;

    
      
      
        if
      
       (parser.next() ==
      
         XmlPullParser.TEXT) {

        result 
      
      =
      
         parser.getText();

        parser.nextTag();

    }

    
      
      
        return
      
      
         result;

}

  ...

}
      
    

六). 跳过你不关注的标签

上面所描述的解析XML步骤中,其中有一步是解析器跳过我们不关注的标签。下面是skip()方法的代码:

      
        private
      
      
        void
      
       skip(XmlPullParser parser) 
      
        throws
      
      
         XmlPullParserException, IOException {

    
      
      
        if
      
       (parser.getEventType() !=
      
         XmlPullParser.START_TAG) {

        
      
      
        throw
      
      
        new
      
      
         IllegalStateException();

    }

    
      
      
        int
      
       depth = 1
      
        ;

    
      
      
        while
      
       (depth != 0
      
        ) {

        
      
      
        switch
      
      
         (parser.next()) {

        
      
      
        case
      
      
         XmlPullParser.END_TAG:

            depth
      
      --
      
        ;

            
      
      
        break
      
      
        ;

        
      
      
        case
      
      
         XmlPullParser.START_TAG:

            depth
      
      ++
      
        ;

            
      
      
        break
      
      
        ;

        }

    }

 }
      
    

它为何这样就能实现跳过的功能呢:

  • 如果当前遇到的不是 START_TAG ,那么抛出一个异常。
  • 它接收 START_TAG ,以及之后遇到的内容,并匹配 END_TAG
  • 为了确保它在正确的 END_TAG 停止,而不是在 START_TAG 之后遇到的第一个标签,它会一直向子标签深度搜索。

因此如果当前标签含有子标签,那么depth的值不会变成0,直到解析器处理了所有在原始的 START_TAG 和与它匹配的 END_TAG 之间的所有标签。例如,考虑该解析器如何略过< author >标签,该标签含有两个子标签 <name>和 <uri>:

  • 第一次while循环,解析器在 <author>之后 遇到了 START_TAG: <name> ,此时 depth的值增加到2。
  • 第二次while循环,解析器遇到了 END_TAG: </name> 。此时 depth的值减少到 1
  • 第三次while循环,解析器遇到了 START_TAG: <uri> 。此时 depth的值增加到2
  • 第四次while循环,解析器遇到了END_TAG: </uri> 。此时 depth 的值减少到 1
  • 最后一次while循环,解析器遇到了 END_TAG: </author>。此时depth的值减少到0,表明 <author>已经被成功忽略了。

七). 处理XML数据

样例代码中,使用了 AsyncTask 获取并解析XML源。这样该过程就不会再UI主线程中执行。当处理执行完毕,应用会更新主Activity( NetworkActivity )的UI。

在下面摘录的代码片段中, loadPage()方法进行了如下的处理:

用XML源的URL初始化一个String变量。

在用户的设置及网络连接允许的情况下,调用 new DownloadXmlTask().execute(url)。这将初始化一个新的 DownloadXmlTask对象( AsyncTask 的子类)并运行它的 execute() 方法,它会下载并解析源并将结果以String的形式返回,显示在UI上。  

      
        public
      
      
        class
      
       NetworkActivity 
      
        extends
      
      
         Activity {

    
      
      
        public
      
      
        static
      
      
        final
      
       String WIFI = "Wi-Fi"
      
        ;

    
      
      
        public
      
      
        static
      
      
        final
      
       String ANY = "Any"
      
        ;

    
      
      
        private
      
      
        static
      
      
        final
      
       String URL = "http://stackoverflow.com/feeds/tag?tagnames=android&sort=newest"
      
        ;

   

    
      
      
        //
      
      
         Whether there is a Wi-Fi connection.
      
      
        private
      
      
        static
      
      
        boolean
      
       wifiConnected = 
      
        false
      
      
        ; 

    
      
      
        //
      
      
         Whether there is a mobile connection.
      
      
        private
      
      
        static
      
      
        boolean
      
       mobileConnected = 
      
        false
      
      
        ;

    
      
      
        //
      
      
         Whether the display should be refreshed.
      
      
        public
      
      
        static
      
      
        boolean
      
       refreshDisplay = 
      
        true
      
      
        ; 

    
      
      
        public
      
      
        static
      
       String sPref = 
      
        null
      
      
        ;



    ...

      

    
      
      
        //
      
      
         Uses AsyncTask to download the XML feed from stackoverflow.com.
      
      
        public
      
      
        void
      
      
         loadPage() {  

      

        
      
      
        if
      
      ((sPref.equals(ANY)) && (wifiConnected ||
      
         mobileConnected)) {

            
      
      
        new
      
      
         DownloadXmlTask().execute(URL);

        }

        
      
      
        else
      
      
        if
      
       ((sPref.equals(WIFI)) &&
      
         (wifiConnected)) {

            
      
      
        new
      
      
         DownloadXmlTask().execute(URL);

        } 
      
      
        else
      
      
         {

            
      
      
        //
      
      
         show error
      
      
                }  

    }
      
    

AsyncTask 的子类:DownloadXmlTask如下所示,它实现了下列 AsyncTask 的方法:

doInBackground() 执行 loadXmlFromNetwork(),它将源的URL作为参数传入。 loadXmlFromNetwork()方法获取并处理源。当它结束以后,它会返回String作为结果。

onPostExecute() 接收结果String并将它显示在UI上。

      
        //
      
      
         Implementation of AsyncTask used to download XML feed from stackoverflow.com.
      
      
        private
      
      
        class
      
       DownloadXmlTask 
      
        extends
      
       AsyncTask<String, Void, String>
      
         {

    @Override

    
      
      
        protected
      
      
         String doInBackground(String... urls) {

        
      
      
        try
      
      
         {

            
      
      
        return
      
       loadXmlFromNetwork(urls[0
      
        ]);

        } 
      
      
        catch
      
      
         (IOException e) {

            
      
      
        return
      
      
         getResources().getString(R.string.connection_error);

        } 
      
      
        catch
      
      
         (XmlPullParserException e) {

            
      
      
        return
      
      
         getResources().getString(R.string.xml_error);

        }

    }



    @Override

    
      
      
        protected
      
      
        void
      
      
         onPostExecute(String result) {  

        setContentView(R.layout.main);

        
      
      
        //
      
      
         Displays the HTML string in the UI via a WebView
      
      

        WebView myWebView =
      
         (WebView) findViewById(R.id.webview);

        myWebView.loadData(result, 
      
      "text/html", 
      
        null
      
      
        );

    }

}
      
    

下面是方法:loadXmlFromNetwork(),它被 DownloadXmlTask调用,它执行下列任务:

  • 初始化一个 StackOverflowXmlParser,它也创建一个装载entry对象的 List entries ),以及 title, url,和 summary,来存储从XML源中相应字段里提取出的数据。
  • 调用 downloadUrl(),它获取源并以 InputStream 的形式返回
  • 使用 StackOverflowXmlParser来解析 InputStream StackOverflowXmlParser会用源中的数据填充 entries这个 List
  • 处理 List ,并将源数据和HTML标记向结合。
  • 返回 HTML字符串,由 AsyncTask onPostExecute() 方法将它 显示在主Activity UI上的。
      
        //
      
      
         Uploads XML from stackoverflow.com, parses it, and combines it with


      
      
        //
      
      
         HTML markup. Returns HTML string.
      
      
        private
      
       String loadXmlFromNetwork(String urlString) 
      
        throws
      
      
         XmlPullParserException, IOException {

    InputStream stream 
      
      = 
      
        null
      
      
        ;

    
      
      
        //
      
      
         Instantiate the parser
      
      

    StackOverflowXmlParser stackOverflowXmlParser = 
      
        new
      
      
         StackOverflowXmlParser();

    List
      
      <Entry> entries = 
      
        null
      
      
        ;

    String title 
      
      = 
      
        null
      
      
        ;

    String url 
      
      = 
      
        null
      
      
        ;

    String summary 
      
      = 
      
        null
      
      
        ;

    Calendar rightNow 
      
      =
      
         Calendar.getInstance(); 

    DateFormat formatter 
      
      = 
      
        new
      
       SimpleDateFormat("MMM dd h:mmaa"
      
        );

        

    
      
      
        //
      
      
         Checks whether the user set the preference to include summary text
      
      

    SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(
      
        this
      
      
        );

    
      
      
        boolean
      
       pref = sharedPrefs.getBoolean("summaryPref", 
      
        false
      
      
        );

        

    StringBuilder htmlString 
      
      = 
      
        new
      
      
         StringBuilder();

    htmlString.append(
      
      "<h3>" + getResources().getString(R.string.page_title) + "</h3>"
      
        );

    htmlString.append(
      
      "<em>" + getResources().getString(R.string.updated) + " " +
      
         

            formatter.format(rightNow.getTime()) 
      
      + "</em>"
      
        );

        

    
      
      
        try
      
      
         {

        stream 
      
      =
      
         downloadUrl(urlString);        

        entries 
      
      =
      
         stackOverflowXmlParser.parse(stream);

    
      
      
        //
      
      
         Makes sure that the InputStream is closed after the app is

    
      
      
        //
      
      
         finished using it.
      
      

    } 
      
        finally
      
      
         {

        
      
      
        if
      
       (stream != 
      
        null
      
      
        ) {

            stream.close();

        } 

     }

    

    
      
      
        //
      
      
         StackOverflowXmlParser returns a List (called "entries") of Entry objects.

    
      
      
        //
      
      
         Each Entry object represents a single post in the XML feed.

    
      
      
        //
      
      
         This section processes the entries list to combine each entry with HTML markup.

    
      
      
        //
      
      
         Each entry is displayed in the UI as a link that optionally includes

    
      
      
        //
      
      
         a text summary.
      
      
        for
      
      
         (Entry entry : entries) {       

        htmlString.append(
      
      "<p><a href='"
      
        );

        htmlString.append(entry.link);

        htmlString.append(
      
      "'>" + entry.title + "</a></p>"
      
        );

        
      
      
        //
      
      
         If the user set the preference to include summary text,

        
      
      
        //
      
      
         adds it to the display.
      
      
        if
      
      
         (pref) {

            htmlString.append(entry.summary);

        }

    }

    
      
      
        return
      
      
         htmlString.toString();

}




      
      
        //
      
      
         Given a string representation of a URL, sets up a connection and gets


      
      
        //
      
      
         an input stream.
      
      
        private
      
       InputStream downloadUrl(String urlString) 
      
        throws
      
      
         IOException {

    URL url 
      
      = 
      
        new
      
      
         URL(urlString);

    HttpURLConnection conn 
      
      =
      
         (HttpURLConnection) url.openConnection();

    conn.setReadTimeout(
      
      10000 
      
        /*
      
      
         milliseconds 
      
      
        */
      
      
        );

    conn.setConnectTimeout(
      
      15000 
      
        /*
      
      
         milliseconds 
      
      
        */
      
      
        );

    conn.setRequestMethod(
      
      "GET"
      
        );

    conn.setDoInput(
      
      
        true
      
      
        );

    
      
      
        //
      
      
         Starts the query
      
      
            conn.connect();

    
      
      
        return
      
      
         conn.getInputStream();

}
      
    

【Android Developers Training】 81. 解析XML数据


更多文章、技术交流、商务合作、联系博主

微信扫码或搜索:z360901061

微信扫一扫加我为好友

QQ号联系: 360901061

您的支持是博主写作最大的动力,如果您喜欢我的文章,感觉我的文章对您有帮助,请用微信扫描下面二维码支持博主2元、5元、10元、20元等您想捐的金额吧,狠狠点击下面给点支持吧,站长非常感激您!手机微信长按不能支付解决办法:请将微信支付二维码保存到相册,切换到微信,然后点击微信右上角扫一扫功能,选择支付二维码完成支付。

【本文对您有帮助就好】

您的支持是博主写作最大的动力,如果您喜欢我的文章,感觉我的文章对您有帮助,请用微信扫描上面二维码支持博主2元、5元、10元、自定义金额等您想捐的金额吧,站长会非常 感谢您的哦!!!

发表我的评论
最新评论 总共0条评论