martes, 18 de noviembre de 2014

Tratamiento de XML en Android



En los siguientes apartados de este manual 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, y en mi caso utilizaré como ejemplo el canal RSS de portada de europapress.com.

Un documento RSS de este feed tiene la estructura siguiente:


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 apartados 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:


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 leído. 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.

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:


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. Como ejemplo de tratamiento de los datos obtenidos mostraremos los titulares de las noticias en un cuadro de texto (txtResultado).



Las líneas 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.

Adicionalmente, 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:


En la línea 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

Pero hay algo más, si ejecutamos el código anterior en una versión de Android anterior a la 3.0 no tendremos ningún problema. La aplicación descargará el XML y lo parseará tal como hemos definido. Sin embargo, si utilizamos una versión de Android 3.0 o superior nos encontraremos con algo similar a esto:


Y si miramos el log de ejecución veremos que se ha generado una excepción del tipo NetworkOnMainThreadException. ¿Qué ha ocurrido? Pues bien, lo que ha ocurrido es que desde la versión 3 de Android se ha establecido una política que no permite a las aplicaciones ejecutar operaciones de larga duración en el hilo principal que puedan bloquear temporalmente la interfaz de usuario. En este caso, nosotros hemos intentado hacer una conexión a la red para descargarnos el XML y Android se ha quejado de ello generando un error.

¿Y cómo arreglamos esto? Pues la solución pasa por mover toda la lógica de descarga y lectura del XML a otro hilo de ejecución secundario, es decir, hacer el trabajo duro en segundo plano dejando libre el hilo principal de la aplicación y por tanto sin afectar a la interfaz. La forma más sencilla de hacer esto en Android es mediante la utilización de las llamadas AsyncTask o tareas asíncronas. Más adelante dedicaremos todo un capítulo a este tema, por lo que ahora no entraremos en detalle y nos limitaremos a ver cómo transformar nuestro código anterior para que funcione en versiones actuales de Android.

Lo que haremos será definir una nueva clase que extienda de AsyncTask y sobrescribiremos sus métodos doInBackground() y onPostExecute().Al primero de ellos moveremos la descarga y parseo del XML, y al segundo las tareas que queremos realizar cuando haya finalizado el primero, en nuestro caso de ejemplo la escritura de los titulares al cuadro de texto de resultado. Quedaría de la siguiente forma:


Por último, sustituiremos el código de nuestra actividad principal por una simple llamada para crear y ejecutar la tarea en segundo plano:


Y ahora sí. Con esta ligera modificación del código nuestra aplicación se ejecutará correctamente en cualquier versión de Android.

En los siguientes apartados 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.



Saludos compañeros, aprovechen la información.









No hay comentarios:

Publicar un comentario en la entrada