L'Extensible Markup Language (XML) è un insieme di regole per la codifica di documenti formato leggibile dalla macchina. XML è un formato popolare per la condivisione di dati su internet.
Siti web che aggiornano di frequente i contenuti, ad esempio siti o blog di notizie spesso forniscono un feed XML per consentire ai programmi esterni di tenersi aggiornati sui contenuti modifiche. Il caricamento e l'analisi dei dati XML sono un'attività comune per i dispositivi connessi alla rete app. Questo argomento spiega come analizzare i documenti XML e utilizzare i relativi dati.
Per scoprire di più sulla creazione di contenuti basati sul web nella tua app per Android, vedi Contenuti basati sul web.
Scegli un parser
Consigliamo XmlPullParser
, che è un servizio efficiente
un modo gestibile di analizzare il codice XML su Android. Android ha due
implementazioni di questa interfaccia:
KXmlParser
, utilizzandoXmlPullParserFactory.newPullParser()
ExpatPullParser
, conXml.newPullParser()
Entrambe le opzioni va bene. La
esempio in questa sezione utilizza ExpatPullParser
e
Xml.newPullParser()
.
Analizzare il feed
Il primo passaggio nell'analisi di un feed consiste nel decidere quali campi ti interessano. L'analizzatore sintattico estrae i dati per questi campi e ignora il resto.
Leggi il seguente estratto di un feed analizzato nell'app di esempio. Ciascuna
post su StackOverflow.com viene visualizzato nella
feed come tag entry
contenente diversi tag nidificati:
<?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>
App di esempio
estrae i dati per il tag entry
e i relativi tag nidificati
title
, link
e summary
.
Creare un'istanza per il parser
Il passaggio successivo nell'analisi di un feed è
creare un'istanza per un parser e avviare il processo di analisi. Questo snippet
inizializza un parser in modo che non elabori gli spazi dei nomi e utilizzi il valore InputStream
fornito come input. Avvia il processo di analisi con una chiamata a
nextTag()
e richiama il metodo
readFeed()
, che estrae ed elabora i dati dell'app
interessato a:
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(); } } ... }
Leggi il feed
Il metodo readFeed()
svolge l'effettivo lavoro di elaborazione
feed. Cerca gli elementi taggati "entry" come punto di partenza per la gestione ricorsiva
durante l'elaborazione del feed. Se non è un tag entry
, un tag viene ignorato. Una volta che l'intera
feed viene elaborato in modo ricorsivo, readFeed()
restituisce un
List
contenenti le voci (inclusi i membri di dati nidificati)
estratti dal feed. L'elemento List
viene quindi restituito
analizzatore sintattico.
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; }
Analizza XML
Per analizzare un feed XML, procedi nel seguente modo:
- Come descritto nella sezione Analizzare il feed, identifica i tag da includere nell'app. Questo
esempio estrae i dati per il tag
entry
e i relativi tag nidificati:title
,link
esummary
. - Crea i seguenti metodi:
- Una "lettura" per ogni tag che vuoi includere, come
readEntry()
ereadTitle()
. L'analizzatore sintattico legge i tag dallo stream di input. Quando incontra un tag denominato, in questo esempio,entry
,title
,link
osummary
, chiama il metodo appropriato per quel tag. In caso contrario, il tag viene ignorato. - Metodi per estrarre i dati per ciascun tipo diverso di tag e per avanzare nella
al tag successivo. In questo esempio, i metodi pertinenti sono i seguenti:
- Per i tag
title
esummary
, il parser chiamareadText()
. Questo metodo estrae i dati per questi tag richiamandoparser.getText()
. - Per il tag
link
, l'analizzatore sintattico estrae i dati dei link innanzitutto determinare se il link è di tipo a cui è interessato. Quindi utilizzaparser.getAttributeValue()
per estrarre il valore del link. - Per il tag
entry
, il parser chiamareadEntry()
. Questo metodo analizza i tag nidificati della voce e restituisce unEntry
con i membri di datititle
,link
esummary
.
- Per i tag
- Un metodo
skip()
helper ricorsivo. Per ulteriori discussioni su questo argomento, vedi Saltare i tag che non ti interessano.
- Una "lettura" per ogni tag che vuoi includere, come
Questo snippet mostra in che modo l'analizzatore sintattico analizza voci, titoli, link e riepiloghi.
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; } ... }
Salta i tag che non ti interessano
L'analizzatore sintattico deve saltare i tag che non sono interessati. Ecco il metodo skip()
del parser:
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; } } }
Ecco come funziona:
- Genera un'eccezione se l'evento attuale non è
START_TAG
. - Utilizza
START_TAG
e tutti gli eventi fino a e inclusi iEND_TAG
corrispondenti. - Tiene traccia della profondità di nidificazione per assicurarsi che si fermi al valore
END_TAG
corretto e non alla il primo tag che incontra dopo il tag originaleSTART_TAG
.
Di conseguenza, se l'elemento corrente ha elementi nidificati, il valore di
Il valore di depth
sarà pari a 0 finché il parser non avrà consumato tutti gli eventi tra
il START_TAG
originale e il END_TAG
corrispondente. Per
ad esempio, considera come l'analizzatore sintattico salta l'elemento <author>
,
che ha 2 elementi nidificati, <name>
e
<uri>
:
- La prima volta tramite il loop
while
, il tag successivo dell'analizzatore sintattico incontri dopo che<author>
è ilSTART_TAG
per<name>
. Il valore didepth
viene incrementato a 2. - La seconda volta tramite il loop
while
, il tag successivo dell'analizzatore sintattico incontri èEND_TAG
</name>
. Il valore perdepth
diminuisci a 1. - La terza volta tramite il loop
while
, il tag successivo dell'analizzatore sintattico incontri èSTART_TAG
<uri>
. Il valore per incrementi didepth
a 2. - La quarta volta tramite il loop
while
, il tag successivo dell'analizzatore sintattico incontri èEND_TAG
</uri>
. Il valore perdepth
diminuisce a 1. - La quinta e ultima volta nel loop
while
, la successiva Il tag rilevato dal parser èEND_TAG
</author>
. Il valore perdepth
diminuisce a 0, a indicare che l'elemento<author>
è stato completato correttamente saltata.
Utilizza dati XML
L'applicazione di esempio recupera e analizza il feed XML in modo asincrono. In questo modo l'elaborazione viene rimossa dal thread dell'interfaccia utente principale. Quando
completa l'elaborazione, l'app aggiorna l'UI nella sua attività principale
NetworkActivity
.
Nel seguente estratto, il metodo loadPage()
svolge queste operazioni:
- Inizializza una variabile stringa con l'URL per il feed XML.
- Consente di richiamare il metodo
downloadXml(url)
se le impostazioni dell'utente e la rete connessione lo consenta. Questo metodo scarica e analizza il feed e restituisce un risultato in formato stringa che visualizzato nell'interfaccia utente.
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. } }
Il metodo downloadXml
chiama i seguenti metodi in Kotlin:
lifecycleScope.launch(Dispatchers.IO)
, che utilizza le coroutine Kotlin per avvia il metodoloadXmlFromNetwork()
sul thread di IO. L'URL del feed viene trasmesso come . Il metodoloadXmlFromNetwork()
recupera ed elabora il feed. Al termine, restituisce una stringa di risultati.withContext(Dispatchers.Main)
, che utilizza le coroutine Kotlin per tornare al thread principale, prende il la stringa restituita e la visualizza nella UI.
Nel linguaggio di programmazione Java, il processo è il seguente:
- Un
Executor
viene eseguito il metodoloadXmlFromNetwork()
su un thread in background. L'URL del feed viene trasmesso come . Il metodoloadXmlFromNetwork()
recupera ed elabora il feed. Al termine, restituisce una stringa di risultati. Handler
chiamapost
per tornare al thread principale, prende il la stringa restituita e la visualizza nella UI.
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); }); }); }
Il metodo loadXmlFromNetwork()
richiamato da
downloadXml
visualizzato nello snippet successivo. Esegue le seguenti operazioni:
- Crea un'istanza per un
StackOverflowXmlParser
. Crea anche variabili unList
diEntry
oggetti (entries
) e pertitle
,url
esummary
, per conservare le estratti dal feed XML per questi campi. - Richiama
downloadUrl()
, che recupera il feed e lo restituisce come unInputStream
. - Utilizza
StackOverflowXmlParser
per analizzareInputStream
.StackOverflowXmlParser
compila unList
dientries
con i dati del feed. - Elabora
entries
List
e combina i dati del feed con il markup HTML. - Restituisce una stringa HTML che viene visualizzata nell'attività principale nell'interfaccia utente.
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(); }