En los siguientes artículos de este Tutorial de Desarrollo para Android vamos a comentar las distintas posibilidades que tenemos a la hora de trabajar con datos en formato XML desde la plataforma Android.
A día de hoy, en casi todas las grandes plataformas de desarrollo existen varias formas de leer y escribir datos en formato XML. Los dos modelos más extendidos son SAX (Simple API for XML) y DOM (Document Object Model). Posteriormente, han ido apareciendo otros tantos, con más o menos éxito, entre los que destaca StAX (Streaming API for XML). Pues bien, Android no se queda atrás en este sentido e incluye estos tres modelos principales para el tratamiento de XML, o para ser más exactos, los dos primeros como tal y una versión análoga del tercero (XmlPull). Por supuesto con cualquiera de los tres modelos podemos hacer las mismas tareas, pero ya veremos cómo dependiendo de la naturaleza de la tarea que queramos realizar va a resultar más eficiente utilizar un modelo u otro.
Antes de empezar, unas anotaciones respecto a los ejemplos que voy a utilizar. Estas técnicas se pueden utilizar para tratar cualquier documento XML, tanto online como local, pero por utilizar algo conocido por la mayoría de vosotros todos los ejemplos van a trabajar sobre los datos XML de un documento RSS online, concretamente sobre el canal RSS de portada de europapress.com.
Un documento RSS de este feed tiene la estructura siguiente:
<rss version="2.0">
<channel>
<title>Europa Press</title>
<link>http://www.europapress.es/</link>
<description>Noticias de Portada.</description>
<image>
<url>http://s01.europapress.net/eplogo.gif</url>
<title>Europa Press</title>
<link>http://www.europapress.es</link>
</image>
<language>es-ES</language>
<copyright>Copyright</copyright>
<pubDate>Sat, 25 Dec 2010 23:27:26 GMT</pubDate>
<lastBuildDate>Sat, 25 Dec 2010 22:47:14 GMT</lastBuildDate>
<item>
<title>Título de la noticia 1</title>
<link>http://link_de_la_noticia_2.es</link>
<description>Descripción de la noticia 2</description>
<guid>http://identificador_de_la_noticia_2.es</guid>
<pubDate>Fecha de publicación 2</pubDate>
</item>
<item>
<title>Título de la noticia 2</title>
<link>http://link_de_la_noticia_2.es</link>
<description>Descripción de la noticia 2</description>
<guid>http://identificador_de_la_noticia_2.es</guid>
<pubDate>Fecha de publicación 2</pubDate>
</item>
...
</channel>
</rss>
Como puede observarse, se compone de un elemento principal <channel> seguido de varios datos relativos al canal y posteriormente una lista de elementos <item> para cada noticia con sus datos asociados.
En estos artículos vamos a describir cómo leer este XML mediante cada una de las tres alternativas citadas, y para ello lo primero que vamos a hacer es definir una clase java para almacenar los datos de una noticia. Nuestro objetivo final será devolver una lista de objetos de este tipo, con la información de todas las noticias. Por comodidad, vamos a almacenar todos los datos como cadenas de texto:
public class Noticia {
private String titulo;
private String link;
private String descripcion;
private String guid;
private String fecha;
public String getTitulo() {
return titulo;
}
public String getLink() {
return link;
}
public String getDescripcion() {
return descripcion;
}
public String getGuid() {
return guid;
}
public String getFecha() {
return fecha;
}
public void setTitulo(String t) {
titulo = t;
}
public void setLink(String l) {
link = l;
}
public void setDescripcion(String d) {
descripcion = d;
}
public void setGuid(String g) {
guid = g;
}
public void setFecha(String f) {
fecha = f;
}
}
Una vez conocemos la estructura del XML a leer y hemos definido las clases auxiliares que nos hacen falta para almacenar los datos, pasamos ya a comentar el primero de los modelos de tratamiento de XML.
SAX en Android
En el modelo SAX, el tratamiento de un XML se basa en un analizador (parser) que a medida que lee secuencialmente el documento XML va generando diferentes eventos con la información de cada elemento leido. Asi, por ejemplo, a medida que lee el XML, si encuentra el comienzo de una etiqueta <title> generará un evento de comienzo de etiqueta, startElement(), con su información asociada, si después de esa etiqueta encuentra un fragmento de texto generará un evento characters() con toda la información necesaria, y así sucesivamente hasta el final del documento. Nuestro trabajo consistirá por tanto en implementar las acciones necesarias a ejecutar para cada uno de los eventos posibles que se pueden generar durante la lectura del documento XML.
Los principales eventos que se pueden producir son los siguientes (consultar aquí la lista completa):
- startDocument(): comienza el documento XML.
- endDocument(): termina el documento XML.
- startElement(): comienza una etiqueta XML.
- endElement(): termina una etiqueta XML.
- characters(): fragmento de texto.
Todos estos métodos están definidos en la clase org.xml.sax.helpers.DefaultHandler, de la cual deberemos derivar una clase propia donde se sobrescriban los eventos necesarios. En nuestro caso vamos a llamarla RssHandler.
public class RssHandler extends DefaultHandler {
private List<Noticia> noticias;
private Noticia noticiaActual;
private StringBuilder sbTexto;
public List<Noticia> getNoticias(){
return noticias;
}
@Override
public void characters(char[] ch, int start, int length)
throws SAXException {
super.characters(ch, start, length);
if (this.notciaActual != null)
builder.append(ch, start, length);
}
@Override
public void endElement(String uri, String localName, String name)
throws SAXException {
super.endElement(uri, localName, name);
if (this.notciaActual != null) {
if (localName.equals("title")) {
noticiaActual.setTitulo(sbTexto.toString());
} else if (localName.equals("link")) {
noticiaActual.setLink(sbTexto.toString());
} else if (localName.equals("description")) {
noticiaActual.setDescripcion(sbTexto.toString());
} else if (localName.equals("guid")) {
noticiaActual.setGuid(sbTexto.toString());
} else if (localName.equals("pubDate")) {
noticiaActual.setFecha(sbTexto.toString());
} else if (localName.equals("item")) {
noticias.add(noticiaActual);
}
sbTexto.setLength(0);
}
}
@Override
public void startDocument() throws SAXException {
super.startDocument();
noticias = new ArrayList<Noticia>();
sbTexto = new StringBuilder();
}
@Override
public void startElement(String uri, String localName,
String name, Attributes attributes) throws SAXException {
super.startElement(uri, localName, name, attributes);
if (localName.equals("item")) {
noticiaActual = new Noticia();
}
}
}
Como se puede observar en el código de anterior, lo primero que haremos será incluir como miembro de la clase la lista de noticias que pretendemos construir, List<Noticia> noticias, y un método getNoticias() que permita obtenerla tras la lectura completa del documento. Tras esto, implementamos directamente los eventos SAX necesarios.
Comencemos por startDocument(), este evento indica que se ha comenzado a leer el documento XML, por lo que lo aprovecharemos para inicializar la lista de noticias y las variables auxiliares.
Tras éste, el evento startElement() se lanza cada vez que se encuentra una nueva etiqueta de apertura. En nuestro caso, la única etiqueta que nos interesará será <item>, momento en el que inicializaremos un nuevo objeto auxiliar de tipo Noticia donde almacenaremos posteriormente los datos de la noticia actual.
El siguiente evento relevante es characters(), que se lanza cada vez que se encuentra un fragmento de texto en el interior de una etiqueta. La técnica aquí será ir acumulando en una variable auxiliar, sbTexto, todos los fragmentos de texto que encontremos hasta detectarse una etiqueta de cierre.
Por último, en el evento de cierre de etiqueta, endElement(), lo que haremos será almacenar en el atributo apropiado del objeto noticiaActual (que conoceremos por el parámetro localName devuelto por el evento) el texto que hemos ido acumulando en la variable sbTexto y limpiaremos el contenido de dicha variable para comenzar a acumular el siguiente dato. El único caso especial será cuando detectemos el cierre de la etiqueta <item>, que significará que hemos terminado de leer todos los datos de la noticia y por tanto aprovecharemos para añadir la noticia actual a la lista de noticias que estamos construyendo.
Una vez implementado nuestro handler, vamos a crear una nueva clase que haga uso de él para parsear mediante SAX un documento XML concreto. A esta clase la llamaremos RssParserSax. Más adelante crearemos otras clases análogas a ésta que hagan lo mismo pero utilizando los otros dos métodos de tratamiento de XML ya mencionados. Esta clase tendrá únicamente un constructor que reciba como parámetro la URL del documento a parsear, y un método público llamado parse() para ejecutar la lectura del documento, y que devolverá como resultado una lista de noticias. Veamos cómo queda esta clase:
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.net.URL;
import javax.xml.parsers.SAXParser;
import java.net.MalformedURLException;
import javax.xml.parsers.SAXParserFactory;
public class RssParserSax
{
private URL rssUrl;
public RssParserSax(String url)
{
try
{
this.rssUrl = new URL(url);
}
catch (MalformedURLException e)
{
throw new RuntimeException(e);
}
}
public List<Noticia> parse()
{
SAXParserFactory factory = SAXParserFactory.newInstance();
try
{
SAXParser parser = factory.newSAXParser();
RssHandler handler = new RssHandler();
parser.parse(this.getInputStream(), handler);
return handler.getNoticias();
}
catch (Exception e)
{
throw new RuntimeException(e);
}
}
private InputStream getInputStream()
{
try
{
return rssUrl.openConnection().getInputStream();
}
catch (IOException e)
{
throw new RuntimeException(e);
}
}
}
Como se puede observar en el código anterior, el constructor de la clase se limitará a aceptar como parámetro la URL del documento XML a parsear a controlar la validez de dicha URL, generando una excepción en caso contrario.
Por su parte, el método parse() será el encargado de crear un nuevo parser SAX mediante sú fábrica correspondiente [lo que se consigue obteniendo una instancia de la fábrica con SAXParserFactory.newInstance() y creando un nuevo parser con factory.newSaxParser()] y de iniciar el proceso pasando al parser una instancia del handler que hemos creado anteriormente y una referencia al documento a parsear en forma de stream.
Para esto último, nos apoyamos en un método privado auxiliar getInputStream(), que se encarga de abrir la conexión con la URL especificada [mediante openConnection()] y obtener el stream de entrada [mediante getInputStream()].
Con esto ya tenemos nuestra aplicación Android preparada para parsear un documento XML online utilizando el modelo SAX. Veamos lo simple que sería ahora llamar a este parser por ejemplo desde nuestra actividad principal:
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
RssParserSax saxparser =
new RssParserSax("http://www.europapress.es/rss/rss.aspx");
List<Noticia> noticias = saxparser.parse();
//Manipulación del array de noticias
//...
}
Las lineas 6 y 9 del código anterior son las que hacen toda la magia. Primero creamos el parser SAX pasándole la URL del documento XML y posteriormente llamamos al método parse() para obtener una lista de objetos de tipo Noticia que posteriormente podremos manipular de la forma que queramos. Así de sencillo.
Tan sólo una anotación final. Para que este ejemplo funcione debemos añadir previamente permisos de acceso a internet para la aplicación. Esto se hace en el fichero AndroidManifest.xml, que quedaría de la siguiente forma:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="net.sgoliver"
android:versionCode="1"
android:versionName="1.0">
<uses-permission
android:name="android.permission.INTERNET" />
<application android:icon="@drawable/icon"
android:label="@string/app_name">
<activity android:name=".AndroidXml"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
<uses-sdk android:minSdkVersion="7" />
</manifest>
En la linea 6 del código podéis ver cómo añadimos el permiso de acceso a la red mediante el elemento <uses-permission> con el parámetro android.permission.INTERNET
Podéis descargar el código fuente de este artículo pulsando aquí.
En los siguientes artículos veremos los otros dos métodos de tratamiento XML en Android que hemos comentado (DOM y StAX) y por último intentaremos comentar las diferencias entre ellos dependiendo del contexto de la aplicación.
ACTUALIZACIÓN: Android propone un modelo SAX alternativo que puede ayudar a simplicar algunas acciones y disminuir la complejidad del handler necesario. En este artículo puedes aprender a utilizar esta nueva variante de SAX para Android.
Curso de Programación Android en PDF
¿Te ha sido de utilidad el Curso de Programación Android? ¿Quieres colaborar de forma económica con el proyecto? Puedes contribuir con cualquier cantidad, unos céntimos, unos euros, cualquier aportación será bienvenida. Además, si tu aportación es superior a una pequeña cantidad simbólica recibirás como agradecimiento un documento con la última versión del curso disponible en formato PDF. Sea como sea, muchas gracias por colaborar!
Más información:
Muchas gracias por el tutorial, una pregunta a la hora de ejecutar la aplicación no me muestra los resultados en la pantalla de android, ¿Tengo que agregar algo mas en el codigo?
Efectivamente, la aplicación del tutorial se limita a recuperar los datos del XML y crear la lista de objetos de tipo Noticia (todo dentro del método onCreate()), pero no los muestra en pantalla.
Si quieres mostrar los resultados en pantalla puedes revisar los artículos anteriores del tutorial para aprender por ejemplo a añadir y personalizar un control de tipo lista para mostrar los resultados del XML.
Hola Me encantan tus tutoriales en este código lees de un fichero que esta en Internet, jo estoy empezando con Android y me gustaría saber como se puede leer de manera local es decir un fichero que este en la tarjeta de memoria del emulador para poder crear mis propias clases y practicar un poquito si me puede orientar te estaré muy agradecido. Gracias de antemano.
Hola, bueno el post! quisiera saber como modificar el codigo para leer cada uno de los tags del xml ejemplo, no solo los que estan contenidos en item… Estoy desarrollando un app donde tengo que hacer esto y estoy teniendo problemas con los listeners…
Muchas gracias
Buenas sgoliver,
Ante todo felicitarte por el curso, es con diferencia el mejor de la red en castellano.
Tengo una duda y espero que puedas ayudarme: necesito leer un fichero XML y mostrarlo en pantalla, pero en mi caso es un fichero local. En el post comentas que éste método es válido tanto para lectura online como offline pero no sé cómo indicarle la ruta a un fichero XML local. ¿Podrías ayudarme?
Gracias.
Saludos.
Hola, muy buen tutorial, sin embargo estoy empezando con todo esto y no sé como personalizar el control de tipo lista para mostrar los resultados ….
¿Sería mucho pedir que nos indicases cómo hacerlo?. Muchas gracias.
Hola motki, para saber cómo mostrar los resultados en una lista puedes consultar los artículos del curso dedicados a los “Controles de Selección”. Saludos.
Hola,muy buen tutorial,es lo que estaba buscando, pero tengo problemas con los acentos y la codificación al hacer la lectura.No se si es problema del xml o de que hay que especificar la codificación en codigo.¿Como se podria hacer?.Gracias de antemano
Una pregunta, en la llamada al parser, no deberias haber inicilizado la lista de noticias??
Leído el articulo, veo que obtienes el fichero XML en caliente.
Estay desarrollando una pequeña aplicación, para aprender un poco en la que requiero de algun tipo de almacén de datos. He pensado en un fichero XML para no complicar de momento mucho la cosa con una bbdd.
El asunto es que en la carpeta de resources no veo claro donde debería ir un fichero de este tipo de lectura escritura y que contendría la información persistente de la aplicación.
Muy bien todo, pero tengo un problema, cuando la etiqueta contiene “enter” solo me lee lo que contiene la primera linea, que podria hacer?, se me ocurre que tiene que ver algo con el metodo characters(), aun asi quisiera que me ayudes. Te agradezco de antemano la ayuda…gracias
Exelente muchas gracias…
Hola, muchas gracias por el tutorial, realmente me ha servido mucho. Hay un detalle que me tiene intrigado, ya que el código de ejemplo funciona muy bien si todos los campos que se leen no están vacios, pero cuando esta algún campo vacio el programa no imprime nada, ¿hay alguna solución para esto?
Gracias.
Como hago para añador y personalizar un control del tipo lista para mostrar un xml
Hola. Muy buenos tus tutoriales. Me encuentro con un problema. Me gustaria que en una clase java recibir un xml como los que hace eclipse y poder (desde java) pintar la pantalla con lo que me trae el xml, el xml va a ser exacto como lo pinta eclipse. Tienes alguna parte donde pueda conseguir eso?, espero pronta respuesta.
Buenísimo el artículo. Muchas gracias por hacer todo este trabajo.
Buenas, si quisiera en vez de leer los nodos hijos de cada item, leer los atributos de cada item… osea:
en vez de :
Título de la noticia 1
http://link_de_la_noticia_2.es
Descripción de la noticia 2
http://identificador_de_la_noticia_2.es
Fecha de publicación 2
¿Como debería de hacerse?
Saludos.
Primero que nada felicidades por tus tutoriales que sigo muchisimo.
He probado tus metodos de lector de xml pero me da muchos problemas en las versiones posteriores a las 3.0. Hago exactamente lo mismo. Me gustaría si pudiera ser que probarás este mismo proyecto en versiones posteriores 3.0 a ver que pudiera ser.
Muchas gracias^^
Buenas, tengo un xml con esta estructura, como puedo hacer para que me lo reconozca, ya que no puedo cambiar la estructura del xml:
Digamos que no tiene la apertura y el cierre de los items.
Gracias
Buenas! en el codigo de RssHandler en la linea 17 empiezas con “builder” y en el ejemplo descargado con sbTexto! me pegé 3 cuartos de horas buscando que fallaba hasta que me dio por bajarme el ejemplo!! jijiji
saludos y muchisisisismas gracias por tus tutoriales!!! para mi, los mejores de la red! :)
Bueno trabajo con este codigo, pero tengo un problema cada vez que ejecuto la aplicacion, se para y me sale el mensaje que la aplicacion a sido parada. No se porque ocurre esto, saludos y gracias, me ocurre en los tres metodos de parseo, unicamente pongo el codigo que viene aqui, ni mas ni menos, y lo intento ejecutar, ya que no lo muestra por pantalla pero me parece muy raro que se pare siempre.
hola sgoliver
tengo una preguntilla como deberia hacer para tratar una etiquieta ![CDATA[ ... ]]
Hola,
Muy buen tutorial, todo bastante explicado.
A parte del error que ya han comentado más arriba del builder.append, todo muy bien.
He implementado estas clases (parecidas) para leer un XML de la carpeta assets, pero me añade espacios antes de los valores que tengo entre etiquetas en el XML.
Lo he solucionado así:
currentQuestion.setRightAnswer(sbText.toString().trim());
Pero no entiendo por qué se añaden esos 6 o 7 espacios antes de las cadenas de texto.
Un saludo.
Hola como estas, verdaderamente esta super todo, hice este ejemplo y si corrió pero me arroja un txtview… Serà que me falte algo para ver el ejemplo completo SAludos…