Extensible Markup Language (XML) ist ein Regelsatz für die Codierung von Dokumenten in maschinenlesbarer Form. XML ist ein beliebtes Format für die Weitergabe von Daten im Internet.
Websites, die ihre Inhalte häufig aktualisieren, wie Nachrichtenwebsites oder Blogs, stellen häufig einen XML-Feed bereit, damit externe Programme über Inhaltsänderungen auf dem Laufenden bleiben. Das Hochladen und Parsen von XML-Daten ist eine häufige Aufgabe bei mit dem Netzwerk verbundenen Anwendungen. In diesem Thema wird erläutert, wie XML-Dokumente analysiert und ihre Daten verwendet werden.
Weitere Informationen zum Erstellen webbasierter Inhalte in Ihrer Android-App finden Sie unter Webbasierte Inhalte.
Parser auswählen
Wir empfehlen XmlPullParser
, eine effiziente und pflegeleichte Möglichkeit zum Parsen von XML unter Android. Android hat zwei Implementierungen dieser Oberfläche:
KXmlParser
, mitXmlPullParserFactory.newPullParser()
ExpatPullParser
, mitXml.newPullParser()
Beide Optionen sind in Ordnung. Im Beispiel in diesem Abschnitt werden ExpatPullParser
und Xml.newPullParser()
verwendet.
Feed analysieren
Der erste Schritt beim Parsen eines Feeds besteht darin, zu entscheiden, welche Felder Sie interessieren. Der Parser extrahiert Daten für diese Felder und ignoriert den Rest.
Sehen Sie sich den folgenden Auszug aus einem geparsten Feed in der Beispiel-App an. Jeder Beitrag auf StackOverflow.com wird im Feed als entry
-Tag angezeigt, das mehrere verschachtelte Tags enthält:
<?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>
Die Beispiel-App extrahiert Daten für das entry
-Tag und die verschachtelten Tags title
, link
und summary
.
Parser instanziieren
Der nächste Schritt beim Parsen eines Feeds besteht darin, einen Parser zu instanziieren und den Parsing-Prozess zu starten. Dieses Snippet initialisiert einen Parser, um keine Namespaces zu verarbeiten und das bereitgestellte InputStream
als Eingabe zu verwenden. Der Parsing-Prozess wird mit einem Aufruf von nextTag()
gestartet und die Methode readFeed()
aufgerufen, die die für die App relevanten Daten extrahiert und verarbeitet:
Kotlin
// We don't use namespaces. private val ns: String? = null class StackOverflowXmlParser { @Throws(XmlPullParserException::class, IOException::class) fun parse(inputStream: InputStream): List<*> { inputStream.use { inputStream -> val parser: XmlPullParser = Xml.newPullParser() parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false) parser.setInput(inputStream, null) parser.nextTag() return readFeed(parser) } } ... }
Java
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(); } } ... }
Feed lesen
Die Methode readFeed()
übernimmt die eigentliche Verarbeitung des Feeds. Als Ausgangspunkt für die rekursive Verarbeitung des Feeds wird nach Elementen mit dem Tag "entry" gesucht. Wenn ein Tag kein entry
-Tag ist, wird es übersprungen. Nachdem der gesamte Feed rekursiv verarbeitet wurde, gibt readFeed()
ein List
mit den Einträgen (einschließlich verschachtelter Datenelemente) zurück, die aus dem Feed extrahiert wurden. Diese List
wird dann vom Parser zurückgegeben.
Kotlin
@Throws(XmlPullParserException::class, IOException::class) private fun readFeed(parser: XmlPullParser): List<Entry> { val entries = mutableListOf<Entry>() parser.require(XmlPullParser.START_TAG, ns, "feed") while (parser.next() != XmlPullParser.END_TAG) { if (parser.eventType != XmlPullParser.START_TAG) { continue } // Starts by looking for the entry tag. if (parser.name == "entry") { entries.add(readEntry(parser)) } else { skip(parser) } } return entries }
Java
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 parsen
Zum Parsen eines XML-Feeds sind folgende Schritte erforderlich:
- Wie unter Feed analysieren beschrieben, ermitteln Sie die Tags, die Sie in Ihre App aufnehmen möchten. In diesem Beispiel werden Daten für das
entry
-Tag und die darin enthaltenen Tagstitle
,link
undsummary
extrahiert. - Erstellen Sie die folgenden Methoden:
- Eine Lesemethode für jedes Tag, das Sie einschließen möchten, z. B.
readEntry()
undreadTitle()
. Der Parser liest Tags aus dem Eingabestream. Wenn es auf ein Tag mit dem Namenentry
,title
,link
odersummary
stößt, wird die entsprechende Methode für dieses Tag aufgerufen. Andernfalls wird das Tag übersprungen. - Methoden zum Extrahieren von Daten für jeden verschiedenen Tag-Typ und zum Fortsetzen des Parsers zum nächsten Tag. In diesem Beispiel sind die folgenden Methoden relevant:
- Für die Tags
title
undsummary
ruft der ParserreadText()
auf. Diese Methode extrahiert Daten für diese Tags durch Aufrufen vonparser.getText()
. - Für das Tag
link
extrahiert der Parser Daten für Links, indem er zuerst ermittelt, ob der Link die Art hat, an der er interessiert ist. Dann wirdparser.getAttributeValue()
verwendet, um den Wert des Links zu extrahieren. - Für das Tag
entry
ruft der ParserreadEntry()
auf. Diese Methode parst die verschachtelten Tags des Eintrags und gibt einEntry
-Objekt mit den Datenelemententitle
,link
undsummary
zurück.
- Für die Tags
- Eine rekursive Hilfsmethode
skip()
. Weitere Informationen zu diesem Thema finden Sie unter Unwichtige Tags überspringen.
- Eine Lesemethode für jedes Tag, das Sie einschließen möchten, z. B.
Dieses Snippet zeigt, wie der Parser Einträge, Titel, Links und Zusammenfassungen parst.
Kotlin
data class Entry(val title: String?, val summary: String?, val link: String?) // 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. @Throws(XmlPullParserException::class, IOException::class) private fun readEntry(parser: XmlPullParser): Entry { parser.require(XmlPullParser.START_TAG, ns, "entry") var title: String? = null var summary: String? = null var link: String? = null while (parser.next() != XmlPullParser.END_TAG) { if (parser.eventType != XmlPullParser.START_TAG) { continue } when (parser.name) { "title" -> title = readTitle(parser) "summary" -> summary = readSummary(parser) "link" -> link = readLink(parser) else -> skip(parser) } } return Entry(title, summary, link) } // Processes title tags in the feed. @Throws(IOException::class, XmlPullParserException::class) private fun readTitle(parser: XmlPullParser): String { parser.require(XmlPullParser.START_TAG, ns, "title") val title = readText(parser) parser.require(XmlPullParser.END_TAG, ns, "title") return title } // Processes link tags in the feed. @Throws(IOException::class, XmlPullParserException::class) private fun readLink(parser: XmlPullParser): String { var link = "" parser.require(XmlPullParser.START_TAG, ns, "link") val tag = parser.name val relType = parser.getAttributeValue(null, "rel") if (tag == "link") { if (relType == "alternate") { link = parser.getAttributeValue(null, "href") parser.nextTag() } } parser.require(XmlPullParser.END_TAG, ns, "link") return link } // Processes summary tags in the feed. @Throws(IOException::class, XmlPullParserException::class) private fun readSummary(parser: XmlPullParser): String { parser.require(XmlPullParser.START_TAG, ns, "summary") val summary = readText(parser) parser.require(XmlPullParser.END_TAG, ns, "summary") return summary } // For the tags title and summary, extracts their text values. @Throws(IOException::class, XmlPullParserException::class) private fun readText(parser: XmlPullParser): String { var result = "" if (parser.next() == XmlPullParser.TEXT) { result = parser.text parser.nextTag() } return result } ...
Java
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; } ... }
Unwichtige Tags überspringen
Der Parser muss Tags überspringen, an denen er nicht interessiert ist. Hier ist die skip()
-Methode des Parsers:
Kotlin
@Throws(XmlPullParserException::class, IOException::class) private fun skip(parser: XmlPullParser) { if (parser.eventType != XmlPullParser.START_TAG) { throw IllegalStateException() } var depth = 1 while (depth != 0) { when (parser.next()) { XmlPullParser.END_TAG -> depth-- XmlPullParser.START_TAG -> depth++ } } }
Java
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; } } }
So funktioniert's:
- Sie gibt eine Ausnahme aus, wenn das aktuelle Ereignis kein
START_TAG
ist. - Er verarbeitet das
START_TAG
und alle Ereignisse bis einschließlich des übereinstimmendenEND_TAG
. - Er erfasst die Verschachtelungstiefe, um sicherzustellen, dass er bei der richtigen
END_TAG
und nicht beim ersten Tag nach dem ursprünglichenSTART_TAG
endet.
Wenn also das aktuelle Element verschachtelte Elemente hat, ist der Wert von depth
erst 0, wenn der Parser alle Ereignisse zwischen der ursprünglichen START_TAG
und der übereinstimmenden END_TAG
verarbeitet. Sehen Sie sich beispielsweise an, wie der Parser das <author>
-Element überspringt, das die beiden verschachtelten Elemente <name>
und <uri>
enthält:
- Beim ersten Mal durch die
while
-Schleife ist das nächste Tag, das der Parser nach<author>
sieht, dasSTART_TAG
für<name>
. Der Wert fürdepth
erhöht sich auf 2. - Das nächste Mal durch die
while
-Schleife ist das nächste Tag, das der Parser sieht, dasEND_TAG
-</name>
. Der Wert fürdepth
verringert sich auf 1. - Beim dritten Mal durch die
while
-Schleife ist das nächste Tag, das der Parser sieht, dasSTART_TAG
-<uri>
. Der Wert fürdepth
erhöht sich in 2. - Beim vierten Mal durch die
while
-Schleife ist das nächste Tag, das der Parser sieht, dasEND_TAG
-</uri>
. Der Wert fürdepth
verringert sich auf 1. - Beim fünften und letzten Mal durch die
while
-Schleife ist das nächste Tag, das der Parser findet, dasEND_TAG
-</author>
. Der Wert fürdepth
verringert sich auf 0. Dies zeigt an, dass das Element<author>
erfolgreich übersprungen wurde.
XML-Daten verarbeiten
Die Beispielanwendung ruft den XML-Feed asynchron ab und parst ihn. Dadurch wird die Verarbeitung aus dem Haupt-UI-Thread entnommen. Wenn die Verarbeitung abgeschlossen ist, aktualisiert die Anwendung die UI in ihrer Hauptaktivität NetworkActivity
.
Im folgenden Auszug führt die Methode loadPage()
Folgendes aus:
- Initialisiert eine Stringvariable mit der URL für den XML-Feed.
- Ruft die Methode
downloadXml(url)
auf, wenn die Einstellungen des Nutzers und die Netzwerkverbindung dies zulassen. Diese Methode lädt den Feed herunter, parst ihn und gibt ein Stringergebnis zurück, das auf der UI angezeigt werden soll.
Kotlin
class NetworkActivity : Activity() { companion object { const val WIFI = "Wi-Fi" const val ANY = "Any" const val SO_URL = "http://stackoverflow.com/feeds/tag?tagnames=android&sort=newest" // Whether there is a Wi-Fi connection. private var wifiConnected = false // Whether there is a mobile connection. private var mobileConnected = false // Whether the display should be refreshed. var refreshDisplay = true // The user's current network preference setting. var sPref: String? = null } ... // Asynchronously downloads the XML feed from stackoverflow.com. fun loadPage() { if (sPref.equals(ANY) && (wifiConnected || mobileConnected)) { downloadXml(SO_URL) } else if (sPref.equals(WIFI) && wifiConnected) { downloadXml(SO_URL) } else { // Show error. } } ... }
Java
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; ... // Asynchronously downloads the XML feed from stackoverflow.com. public void loadPage() { if((sPref.equals(ANY)) && (wifiConnected || mobileConnected)) { downloadXml(URL); } else if ((sPref.equals(WIFI)) && (wifiConnected)) { downloadXml(URL); } else { // Show error. } }
Mit der Methode downloadXml
werden die folgenden Methoden in Kotlin aufgerufen:
lifecycleScope.launch(Dispatchers.IO)
, das Kotlin-Coroutinen verwendet, um die MethodeloadXmlFromNetwork()
im E/A-Thread zu starten. Die Feed-URL wird dabei als Parameter übergeben. Der Feed wird mit der MethodeloadXmlFromNetwork()
abgerufen und verarbeitet. Wenn der Vorgang abgeschlossen ist, wird ein Ergebnisstring zurückgegeben.withContext(Dispatchers.Main)
verwendet Kotlin-Koroutinen, um zum Hauptthread zurückzukehren, und zeigt den zurückgegebenen String in der UI an.
In der Programmiersprache Java läuft der Prozess wie folgt ab:
- Ein
Executor
führt die MethodeloadXmlFromNetwork()
in einem Hintergrundthread aus. Die Feed-URL wird dabei als Parameter übergeben. Der Feed wird mit der MethodeloadXmlFromNetwork()
abgerufen und verarbeitet. Wenn der Vorgang abgeschlossen ist, wird ein Ergebnisstring zurückgegeben. - Ein
Handler
ruftpost
auf, um zum Hauptthread zurückzukehren, nimmt den zurückgegebenen String an und zeigt ihn in der UI an.
Kotlin
// Implementation of Kotlin coroutines used to download XML feed from stackoverflow.com. private fun downloadXml(vararg urls: String) { var result: String? = null lifecycleScope.launch(Dispatchers.IO) { result = try { loadXmlFromNetwork(urls[0]) } catch (e: IOException) { resources.getString(R.string.connection_error) } catch (e: XmlPullParserException) { resources.getString(R.string.xml_error) } withContext(Dispatchers.Main) { setContentView(R.layout.main) // Displays the HTML string in the UI via a WebView. findViewById<WebView>(R.id.webview)?.apply { loadData(result?: "", "text/html", null) } } } }
Java
// Implementation of Executor and Handler used to download XML feed asynchronously from stackoverflow.com. private void downloadXml(String... urls) { ExecutorService executor = Executors.newSingleThreadExecutor(); Handler handler = new Handler(Looper.getMainLooper()); executor.execute(() -> { String result; try { result = loadXmlFromNetwork(urls[0]); } catch (IOException e) { result = getResources().getString(R.string.connection_error); } catch (XmlPullParserException e) { result = getResources().getString(R.string.xml_error); } String finalResult = result; handler.post(() -> { setContentView(R.layout.main); // Displays the HTML string in the UI via a WebView. WebView myWebView = (WebView) findViewById(R.id.webview); myWebView.loadData(finalResult, "text/html", null); }); }); }
Die Methode loadXmlFromNetwork()
, die über downloadXml
aufgerufen wird, wird im nächsten Snippet gezeigt. Dabei geschieht Folgendes:
- Instanziiert ein
StackOverflowXmlParser
. Außerdem werden Variablen für eineList
mitEntry
-Objekten (entries
) sowie fürtitle
,url
undsummary
erstellt, um die aus dem XML-Feed extrahierten Werte für diese Felder aufzunehmen. - Ruft
downloadUrl()
auf, wodurch der Feed abgerufen und alsInputStream
zurückgegeben wird. - Verwendet
StackOverflowXmlParser
, umInputStream
zu parsen.StackOverflowXmlParser
füllt einList
vonentries
mit Daten aus dem Feed. - Verarbeitet das
entries
-List
und kombiniert die Feeddaten mit HTML-Markup. - Gibt einen HTML-String zurück, der auf der UI der Hauptaktivität angezeigt wird.
Kotlin
// Uploads XML from stackoverflow.com, parses it, and combines it with // HTML markup. Returns HTML string. @Throws(XmlPullParserException::class, IOException::class) private fun loadXmlFromNetwork(urlString: String): String { // Checks whether the user set the preference to include summary text. val pref: Boolean = PreferenceManager.getDefaultSharedPreferences(this)?.run { getBoolean("summaryPref", false) } ?: false val entries: List<Entry> = downloadUrl(urlString)?.use { stream -> // Instantiates the parser. StackOverflowXmlParser().parse(stream) } ?: emptyList() return StringBuilder().apply { append("<h3>${resources.getString(R.string.page_title)}</h3>") append("<em>${resources.getString(R.string.updated)} ") append("${formatter.format(rightNow.time)}</em>") // 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. entries.forEach { entry -> append("<p><a href='") append(entry.link) append("'>" + entry.title + "</a></p>") // If the user set the preference to include summary text, // adds it to the display. if (pref) { append(entry.summary) } } }.toString() } // Given a string representation of a URL, sets up a connection and gets // an input stream. @Throws(IOException::class) private fun downloadUrl(urlString: String): InputStream? { val url = URL(urlString) return (url.openConnection() as? HttpURLConnection)?.run { readTimeout = 10000 connectTimeout = 15000 requestMethod = "GET" doInput = true // Starts the query. connect() inputStream } }
Java
// 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; // Instantiates 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(); }