Uso de HTTP con AsyncTask

 

En el ejercicio anterior hemos configurado el modo estricto para que nos permita realizar accesos a la red desde el hilo principal. Aunque en la mayoría de los casos la interacción con el servidor es inferior a 2 décimas de segundo, podrían darse algunos casos en que la interacción fuera superior a un segundo (servidores sobrecargados o redes muy lentas). En estos casos, la interfaz de usuario permanecería bloqueada un tiempo excesivo.

En el capítulo 5 hemos aprendido a utilizar la clase AsyncTaskpara resolver estos problemas. El siguiente ejercicio nos muestra cómo puede utilizarse en este caso:

Ejercicio: Búsquedas en Google con HTTP  y AsyncTask

1.    Añade la siguiente clase dentro de MainActivitydel ejercicio anterior:

class BuscarGoogle extends AsyncTask<String, Void, String> {
   private ProgressDialog progreso;

   @Override protected void onPreExecute() {
      progreso = new ProgressDialog(MainActivity.this);
      progreso.setProgressStyle(ProgressDialog.STYLE_SPINNER);
      progreso.setMessage("Accediendo a Google...");
      progreso.setCancelable(false); // false: no muestra botón cancelar
      progreso.show();
   }

   @Override protected String doInBackground(String... palabras) {
      try {
         return resultadosGoogle(palabras[0]);
      } catch (Exception e) {
         cancel(false); //true: interrumpimos hilo, false: dejamos termine
         Log.e("HTTP", e.getMessage(), e);
         return null;
      }
   }

   @Override protected void onPostExecute(String res) {
      progreso.dismiss();
      salida.append(res + "\n");
   }
   
   @Override protected void onCancelled() {
      progreso.dismiss();
      salida.append("Error al conectar\n");
   }
}  

 

Observa como la nueva clase extiende AsyncTask<String, Void, String>. Se han escogido estos tres tipos de datos porque la entrada de la tarea será un String con las palabras que hay que buscar; no se necesita información de progreso y la salida será un String con el número de veces que se encuentran. En el método onPreExecute() se visualiza un ProgressDialog con estilo SPINNER. El método doInBackground() es el único que se ejecuta en un nuevo hilo. En el nos limitamos a llamar al método que habíamos programado en el ejercicio anterior para hacer la consulta. En caso de que se produzca una excepción cancelaremos la tarea, mostraremos el error en el Log y no devolveremos nada. El resto de los métodos se llaman según la tarea haya concluido o haya sido cancelada.

2.    Añade el siguiente método:

public void buscar2(View view){
   String palabras = entrada.getText().toString();
   salida.append(palabras + "--");
   new BuscarGoogle().execute(palabras);
}

3.   Abre el layout activity_main.xml y añade un botón con texto: “buscar en Google con AsyncTask” y con un valor para onClick: “buscar2”.

4.   Verifica que la aplicación funciona correctamente.

 

Preguntas de repaso: El protocolo HTTP

 

Servicios web

 

La W3C define "servicio web" como un sistemade software diseñado para permitir interoperabilidad máquina a máquina en una red. Se trata de API que son publicadas, localizadas e invocadas a través de la web. Es decir, una vez desarrolladas, son instaladas en un servidor, y otras aplicaciones (u otros servicios web) pueden descubrirlas desde otros ordenadores de Internet e invocar uno de sus servicios.

Como norma general, el transporte de los datos se realiza a través del protocolo HTTP y la representación de los datos mediante XML. Sin embargo, no hay reglas fijas en los servicios web y en la práctica no tiene por qué ser así.

Una de las grandes ventajas de este planteamiento es que es tecnológicamente neutral. Es decir, podemos utilizar un servicio web sin importarnos el sistema operativo o el lenguaje en el que fue programado. Además, al apoyarse sobre el protocolo HTTP, puede utilizar los sistemas de seguridad (https) y presenta pocos problemas con cortafuegos, al utilizar puertos que suelen estar abiertos (80 o 8080).

Como inconveniente podemos resaltar que, dado que el intercambio de datos se realiza en formato de texto (XML), tiene menor rendimiento que otras alternativas como RMI (Remote Method Invocation), CORBA (Common Object Request Broker Architecture) o DCOM (Distributed Component Object Model). Además, el hecho de apoyarse en HTTP hace que resulte complicado para un cortafuego filtrar este tipo de tráfico. ¿No acabamos de decir que esto era una ventaja? Es posible que lo que para un desarrollador sea una ventaja, para un administrador de red sea un inconveniente.

 

 

- Alternativas en los servicios web

Como acabamos de ver, el término “servicio web” resulta difícil de definir de forma precisa. En torno a este concepto se han desarrollado varias propuestas bastante diferentes entre sí. En este apartado estudiaremos las dos alternativas que están teniendo más relevancia en la actualidad: SOAP y REST. No obstante, dada la complejidad que surge de estas propuestas, resulta interesante centrar algunos conceptos antes de empezar a describir estas alternativas. Comenzaremos indicando que existen tres enfoques diferentes a la hora de definir un servicio web. Es lo que se conoce como arquitectura del servicio web.

 

Llamadas a procedimiento remotos (RPC): Se enfoca el servicio web como una colección de operaciones o procedimientos que pueden ser invocados desde una máquina diferente de donde se ejecutan. Como RPC es una extensión directa del paradigma de llamadas a funciones, resulta sencillo de entender para un programador. Al ser una de las primeras alternativas que se implementó, se conocen como servicios web de primera generación.

Arquitectura orientada a servicios (SOAP): En el planteamiento anterior, RPC, la unidad básica de interacción es la operación. En este nuevo planteamiento, la unidad de interacción pasa a ser el mensaje. Por lo tanto, en muchos casos se conocen como servicios orientados a mensaje. Cada uno de los mensajes que vamos a utilizar ha de ser definido siguiendo una estricta sintaxis expresada en XML. En la actualidad se trata de la arquitectura más extendida, soportada por la mayoría del software de servicios web.

Transferencia de estado representacional(REST): En los últimos años se está popularizando este nuevo planteamiento, que se caracteriza principalmente por su simplicidad. En REST se utiliza directamente el protocolo HTTP, por medio de sus operaciones GET, POST, PUT y DELETE. En consecuencia, esta arquitectura se centra en la solicitud de recursos, en lugar de las operaciones o los mensajes de las alternativas anteriores.

- Servicios web basados en SOAP

SOAP (Simple Object Access Protocol) es el protocolo más utilizado en la actualidad para implementar servicios web. Fue creado por Microsoft, IBM y otros, aunque en la actualidad está bajo el auspicio de la W3C.

Utiliza como transporte HTTP, aunque también es posible utilizar otros métodos de transporte, como el correo electrónico. Los mensajes del protocolo se definen utilizando un estricto formato XML, que ha de ser consensuado por ambas partes. A continuación se muestra un posible ejemplo de mensaje SOAP

<soapenv:Envelope
    xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
    xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/03/addressing">
    <soapenv:Header>
        <wsa:MessageID>
            uuid:920C5190-0B8F-11D9-8CED-F22EDEEBF7E5
        </wsa:MessageID>
        <wsa:To>
            http://localhost:8081/axis/services/BankPort
        </wsa:To>
    </soapenv:Header>
    <soapenv:Body>
        <axis2:echo xmlns:axis2="http://ws.apache.org/axis2">
            Hello World
        </axis2:echo>
    </soapenv:Body>
</soapenv:Envelope>

Un mensaje SOAP contiene una etiqueta <Envelope>, que encapsula las etiquetas <Header> y <Body>. La etiqueta <Header> es opcional y encapsula aspectos relativos a calidad del servicio, como seguridad, esquemas de direccionamiento, etc. La cabecera <Body> es obligatoria y contiene la información que las aplicaciones quieren intercambiar.

SOAP proporciona una descripción completa de las operaciones que puede realizar un nodo mediante una descripción WSDL (Web Services Description Language), por supuesto codificada en XML. En uno de los siguientes apartados crearemos un servicio web y podremos estudiar el fichero WSDL correspondiente.

Aunque SOAP está ampliamente extendido como estándar para el desarrollo de servicios web, no resulta muy adecuado para ser utilizado en Android. Esto es debido a la complejidad introducida, supone una sobrecarga que implica un menor rendimiento frente a otras alternativas como REST. Además, Android no incorpora las librerías necesarias para trabajar con SOAP.

No obstante, es posible que ya dispongas de un servidor basado en SOAP y necesites implementar un cliente en Android. En tal caso puedes utilizar las librerías kSOAP2 (http://ksoap2.sourceforge.net/).

- Servicios web basados en REST

 

En primer lugar conviene destacar que el término REST se refiere a una arquitectura en lugar de a un protocolo en concreto, como es el caso de SOAP. A diferencia de SOAP, no vamos a añadir una capa adicional a la pila de protocolos, sino que utilizaremos directamente el protocolo HTTP. Siendo estrictos, la arquitectura REST no impone el uso de HTTP; no obstante, en la práctica se entiende que un servicio web basado en REST es aquel que se implementa directamente sobre la web.

Este planteamiento supone seguir los principios de la aplicación WWW, pero en lugar de solicitar páginas web solicitaremos servicios web. Los principios básicos de la aplicación WWW y, por tanto, los de REST son:

  • Transporte de datos mediante HTTP, utilizando las operaciones de este protocolo, que son GET, POST, PUT y DELETE.
  •  Los diferentes servicios son invocados mediante el espacio de URI unificado. Como ya se ha tratado en este libro, una URI identifica un recurso en Internet. Este sistema ha demostrado ser flexible, sencillo y potente al mismo tiempo. Se cree que fue uno de los principales factores que motivó el éxito de WWW.
  •  La codificación de datos es identificada mediante tipos MIME (text/html, image/gif, etc.), aunque el tipo de codificación preferido es XML (text/xml).

Ejercicio paso a paso: Comparativa entre una interacción SOAP y REST.

La empresa WebserviceX.NET ofrece un servicio web, GetIPService, que nos permite conocer, a partir de una dirección IP, el país al que pertenece. Este servicio puede ser utilizado tanto con REST como con SOAP, lo cual nos va a permitir comparar ambos mecanismos. Más todavía, dentro de REST tenemos dos opciones para mandar datos al servidor: el método GET y el método POST. El servicio que vamos a probar nos permite las dos variantes, lo que nos permitirá comparar ambos mecanismos.

 

1.     Abre un navegador web y accede a la URL:

http://www.webservicex.net/geoipservice.asmx/GetGeoIP?IPAddress=158.42.38.1

2.     Verifica que el resultado es similar al siguiente:

<?xml version="1.0" encoding="utf-8"?>
<GeoIP xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:xsd="http://www.w3.org/2001/XMLSchema"
       xmlns="http://www.webservicex.net/">
   <ReturnCode>1</ReturnCode>
   <IP>158.42.38.1</IP>
   <ReturnCodeDetails>Success</ReturnCodeDetails>
   <CountryName>European Union</CountryName>
   <CountryCode>EU</CountryCode>
</GeoIP>

 

3.     Prueba otras IP al azar y verifica a que países pertenecen.

4.     Vamos a emular el protocolo HTTP de forma similar a como lo hemos hecho en secciones anteriores. Desde un intérprete de comandos (símbolo del sistema/shell) escribe:

 telnet www.webservicex.net 80

5.     Cuando se establezca la conexión teclea exactamente el siguiente código seguido de un salto de línea adicional:

 GET /geoipservice.asmx/GetGeoIP?IPAddress=158.42.38.1 HTTP/1.1

       Host: www.webservicex.net

NOTA: También puedes cortar el texto y pegarlo. Para pegar sobre la ventana de símbolo de sistema de Windows has de pulsar con el botón derecho del ratón y seleccionar Pegar.

6.     El resultado ha de parecerse al anterior aunque al principio el servidor enviará sus cabeceras:

HTTP/1.1 200 OK

Cache-Control: private, max-age=0

Content-Length: 374

Content-Type: text/xml; charset=utf-8

Server: Microsoft-IIS/7.0

X-AspNet-Version: 4.0.30319

X-Powered-By: ASP.NET

Date: Mon, 30 Jan 2012 19:28:55 GMT

 

7.     Como acabamos de ver, el protocolo HTTP permite enviar información al servidor utilizando el método GET e introduciendo un carácter “?” al final de la URL seguido de los parámetros. El protocolo HTTP también permite mandar información con el método POST. El servicio web que estamos utilizando nos permite las dos alternativas. Veamos en qué consiste el método POST. Escribe en el intérprete de comandos:

telnet www.webservicex.net 80

8.     Cuando se establezca la conexión pega los siguientes caracteres:

POST /geoipservice.asmx/GetGeoIP HTTP/1.1

Host: www.webservicex.net

Content-Type: application/x-www-form-urlencoded

Content-Length: 21

 

IPAddress=158.42.38.1

Como puedes observar la información enviada es la misma, aunque ahora en lugar de adjuntarla a la URL se manda tras las cabeceras separada por una línea en blanco.

NOTA: La cabecera Content-Length: es obligatoria. Indica el número de caracteres enviados. En caso de que cambiara la longitud de la dirección IP tendrías que ajustarlo.

9.     El servidor está esperando nuevos comandos. Pulsa <Ctrl-C> para cerrarla conexión.

10.     Ahora vamos a usar el mismo servicio aunque mediante el protocolo SOAP 1.1 (NOTA:también es posible utilizar SOAP 1.2). Escribe en el interprete de comandos:

telnet www.webservicex.net 80

11.  Cuando se establezca la conexión pega los siguientes caracteres:

POST /geoipservice.asmx HTTP/1.1

Host: www.webservicex.net

Content-Type: text/xml; charset=utf-8

Content-Length: 371

SOAPAction: "http://www.webservicex.net/GetGeoIP"

 

<?xml version="1.0" encoding="utf-8"?>

<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:xsd="http://www.w3.org/2001/XMLSchema"xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">

  <soap:Body>

    <GetGeoIP xmlns="http://www.webservicex.net/">

      <IPAddress>158.42.38.1</IPAddress>

    </GetGeoIP>

  </soap:Body>

</soap:Envelope>

12.  Pulsa <Ctrl-C> para cerrar la conexión. Compara la información mandada en SOAP con el caso anterior.  El resultado obtenido ha de ser similar al siguiente:

HTTP/1.1 200 OK

Cache-Control: private, max-age=0

Content-Length: 514

Content-Type: text/xml; charset=utf-8

Server: Microsoft-IIS/7.0

X-AspNet-Version: 4.0.30319

X-Powered-By: ASP.NET

Date: Mon, 30 Jan 2012 20:07:55 GMT

 

<?xml version="1.0" encoding="utf-8"?>

<soap:Envelope

 xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"

 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

 xmlns:xsd="http://www.w3.org/2001/XMLSchema">

  <soap:Body>

   <GetGeoIPResponse xmlns="http://www.webservicex.net/">

      <GetGeoIPResult>

        <ReturnCode>1</ReturnCode>

        <IP>158.42.38.1</IP>

        <ReturnCodeDetails>Success</ReturnCodeDetails>

        <CountryName>European Union</CountryName>

        <CountryCode>EU</CountryCode>

      </GetGeoIPResult>

    </GetGeoIPResponse>

  </soap:Body>

</soap:Envelope>

 

Recursos adicionales: Ejemplos de algunos servicios Web gratuitos

En la siguiente tabla te mostramos una lista con algunos servicios Web:

 

Nombre

Descripción

Empresa

tipo (codific.)

GetIPService A partir de una IP nos indica el país http://www.webservicex.net/geoipservice.asmx/GetGeoIP?IPAddress=158.42.38.1 WebserviceX.NET SOAP /REST
Google Custom Search

Búsqueda en Web con respuesta JSON o Atom.

https://www.googleapis.com/customsearch/v1?key=KEY&cx=01757666251246:omuauf_lfve&q=Antonio+Banderas
Google REST
(JSON/ XML)

Books API

Búsqueda y altas de libros. No hay que registrar clave. Obsoleto. (aunque sigue funcionando).

http://books.google.com/books/feeds/volumes?q=nadal

Google

REST 
(XML)

Books API (nueva)

Búsqueda y altas de libros.

https://www.googleapis.com/books/v1/volumes?q=nadal

Google

REST (JSON)

Google Maps Geocoding

 

A partir de unas dirección nos da la longitud y latitud. O a la inversa.
http://maps.google.com/maps/api/geocode/xml?address=Gandia

Google

REST (JSON, XML)

Yahoo Finance API

Cotizaciones en bolsa: (www.jarloo.com/yahoo_finance/)

http://finance.yahoo.com/d/quotes.csv?s=BBVA+SAN&f=na

Cambio entre divisas:

http://finance.yahoo.com/d/quotes.csv?s=USDEUR=X&f=l1

Yahoo

REST (CSV)

Valencia Datos Abiertos

Monumentos, fallas, contaminación, trafico, aparca­miento, autobus, recursos sociales, urbanismo, etc.

http://gobiernoabierto.valencia.es/
Ayunta­miento de Valencia REST (JSON, KML, …)
Datos abiertos en España Portal que recopila datos en abierto de  administra­ciones españolas: ministerios, comunidades…
http://datos.gob.es/
Gobierno de España SOAP /REST (XML,…)

Además de estas, te recomendamos que visites la web de la empresa WebserviceX.NET, que ofrece decenas de servicios web gratuitos. Otro sitio interesante es el que ofrece el Ministerio de Fomento de España[1]. Aquí encontrarás un directorio con centenares de servicios web geográficos ofrecidos por instituciones públicas (por ejemplo, el precio del combustible en estaciones de servicio).

- Acceso a servicios web de terceros

 


 

En este apartado nos centraremos en cómo utilizar un servicio REST publicado por un tercero. Como se ha comentado el acceso a un servicio SOAP resulta algo más complicado y Android no dispone de librerías para facilitarnos el trabajo.

Ejercicio: Acceso a servicios Web de búsqueda de libros.

En concreto vamos a utilizar el servicio Books API, que permite buscar y gestionar libros de la base de datos de Google. Es un servicio obsoleto, aunque actualmente operativo. No usaremos el nuevo servicio que ofrece Google (nueva Books API), dado que este nos da el resultado en JSON en lugar de en XML y en este libro no hemos estudiado el trabajo con JSON.

 

1.     Para probar el servicio Web abre un navegador web y accede a la siguiente URL:

  http://books.google.com/books/feeds/volumes?q=antonio+banderas

El resultado ha de ser similar a:

<?xml version='1.0' encoding='UTF-8'?>
<feed xmlns:openSearch="http://a9.com/-/spec/opensearchrss/1.0/"
      xmlns:gbs=http://schemas.google.com/books/2008
      xmlns:dc=http://purl.org/dc/terms
      xmlns:batch=http://schemas.google.com/gdata/batch
      xmlns:gd=http://schemas.google.com/g/2005
      xmlns="http://www.w3.org/2005/Atom" >
   <id >http://www.google.com/books/feeds/volumes</id>
   <updated >2012-01-30T23:56:34.000Z</updated>
   <category scheme=http://schemas.google.com/g/2005#kind
      term="http://schemas.google.com/books/2008#volume" />
    <title type="text" > Search results for antonio banderas</title>
   <link href="http://www.google.com" rel="alternate" type="text/html"/>
   <link href=http://www.google.com/books/feeds/volumes
      rel="http://schemas.google.com/g/2005#feed"
      type="application/atom+xml" />
   <link
      href=http://www.google.com/books/feeds/volumes?q=antonio+banderas
      rel="self" type="application/atom+xml" />
   <link href="http://www.google.com/books/feeds/volumes?q=antonio
      +banderas&amp; start-index=11&amp;max-results=10" rel="next"
      type="application/atom+xml" />
   <author><name>Google Books Search</name>
      <uri>http://www.google.com</uri></author>
   <generator version="beta" >Google Book Search data API</generator>
   <openSearch:totalResults >596</openSearch:totalResults>
   <openSearch:startIndex >1</openSearch:startIndex>
   <openSearch:itemsPerPage >10</openSearch:itemsPerPage>
   <entry >
      <id >http://www.google.com/books/feeds/volumes/_Y81OAAACAAJ</id>
      <updated >2012-01-30T23:56:34.000Z</updated>
   …

En el resultado obtenido localiza la etiqueta totalResults. Representan los libros encontrados que contienen la búsqueda “antonio banderas”.

2.     Abre el proyecto HTTP creado en el ejercicio Utilizando HTTP desde Android”. Ahora utilizaremos un servicio web de búsqueda de libros, en lugar de buscar la información en una página web.

 

3.     En la actividad principal añade el siguiente método:

String resultadosSW(String palabras) throws Exception {
   URL url = new URL("http://books.google.com/books/feeds/volumes?q=\""
          + URLEncoder.encode(palabras, "UTF-8") + "\"");
   SAXParserFactory fabrica = SAXParserFactory.newInstance();
   SAXParser parser = fabrica.newSAXParser();
   XMLReader lector = parser.getXMLReader();
   ManejadorXML manejadorXML = new ManejadorXML();
   lector.setContentHandler(manejadorXML);
   lector.parse(new InputSource(url.openStream()));
   return manejadorXML.getTotalResults();
}

El método comienza creando la URL de acceso. El resto del código utiliza las librerías org.xml.sax.* para realizar un proceso de parser sobre el código XML de la URL y así extraer la información que nos interesa. Este proceso se ha explicado en el capítulo anterior. El trabajo que sí que tendremos que realizar en función del formato XML específico será la creación de la clase XMLHandler. Una vez finalizado el parser, podemos llamar al método getTotalResults() de nuestro manejador para que nos devuelva la información que nos interesa.

4.     Realiza una copia del método buscar() cambiando el nombre por buscar3(). En este método reemplaza resultadosGoogle() por resultadosSW().

5.     Abre el layout activity_main.xml y añade un botón con texto: “buscar en SW libros” y con un valor para onClick: “buscar3”.

 

6.     A continuación mostramos la definición de la clase ManejadorXML. Copia este código dentro de la clase MainActivity:

 

public class ManejadorXML extends DefaultHandler {

   private String totalResults;
   private StringBuilder cadena = new StringBuilder();
   
   public String getTotalResults() {
      return totalResults;
   }

   @Override
   public void startElement(String uri, String nombreLocal, String
         nombreCualif, Attributes atributos) throws SAXException {
      cadena.setLength(0);
   }

   @Override
   public void characters(char ch[], int comienzo, int lon){
      cadena.append(ch, comienzo, lon);
   }

   @Override
   public void endElement(String uri, String nombreLocal,
      String nombreCualif) throws SAXException {
      if (nombreLocal.equals("totalResults")) {
         totalResults = cadena.toString();
      }
   }
}

Para procesar el fichero XML extendemos la clase DefaultHandler y rescribimos muchos de sus métodos: en startElement() inicializamos la variable cadena cada vez que empieza una etiqueta; en el método characters() añadimos el contenido que aparece dentro de la etiqueta; finalmente, en endElement() recogemos el valor acumulado en cadena cada vez que termina una etiqueta. Como hemos comentado, de todo el código XML que vamos a procesar, solo nos interesa el contenido de la etiqueta <totalResults>.

7.     Verifica el funcionamiento de la aplicación.

 

Práctica:  Convertidor de divisas mediante un servicio web

Uno de los servicios Web, ofrecidos en Yahoo Finance API, permite obtener la ratio de conversión de divisas según el cambio actual. A continuación se muestra un ejemplo de uso para obtener la ratio euro-dólar:

http://finance.yahoo.com/d/quotes.csv?s=EURUSD=X&f=l1

1.     Retoma el diseño de layouts de la eurocalculadora, realizado en el capítulo 2, para un nuevo proyecto de conversión de divisas. En una primera fase solo se realizará la conversión de euros a dólares, aplicando la ratio obtenida a través del servicio web indicado.

2.     En una segunda fase puedes permitir que el usuario seleccione la divisa de entrada (reemplazando en la URL “EUR”) y la de salida (reemplazando en la URL “USD”). Puedes encontrar una lista de las divisas disponibles en [1].

 

- Un servicio web con Apache, PHP y MySQL

A la hora de desarrollar una aplicación distribuida, una de las alternativas más utilizadas en la actualidad son los servicios web. A lo largo de este apartado y el siguiente aprenderás cómo crear tus propios servicios web e instalarlos en un servidor web. Como no podría ser de otra manera, el ejemplo seleccionado es el mismo que ya hemos implementado en varias ocasiones: un almacén de las mejores puntuaciones de Asteroides.

No resultaría demasiado complicado rescribir el ejemplo del servidor de ECHO descrito en un apartado anterior para crear nuestro propio servidor web. No obstante, si estamos trabajando en un entorno empresarial, esta alternativa no sería la más adecuada. En este entorno es mucho más recomendable utilizar un servidor web de uso comercial, como Apache.Esta solución resulta mucho más segura y escalable.

En el siguiente apartado aprenderemos a crear un servicio web usando la combinación Apache, Tomcat y Axis2. Esta opción tiene varias ventajas: toda la programación la hacemos con un mismo lenguaje (Java), el código que tenemos que escribir es muy limpio y permite publicar el servicio con estilo REST o SOAP.

En este apartado estudiaremos otra alternativa, que consiste en usar el trinomio Apache, PHP y MySQL. Tiene sus inconvenientes, como la necesidad de usar un nuevo lenguaje (PHP), y el código a usar es más rudimentario. Sin embargo, presenta importantes ventajas: se trata de la solución más extendida. La mayoría de las empresas ya disponen de un servidor web basado en Apache, PHP y MySQL. Siempre será conveniente montar el servicio web usando la misma tecnología que la que usamos en el sitio web. Por otra parte, la mayoría de los servidores de hosting comerciales trabajan con PHP y MySQL.

 

Ejercicio: Instalación de Apache, PHP y MySQL

En este ejercicio vamos a instalar en nuestro propio ordenador el servidor web Apache con su extensión para poder ejecutar código PHP. Además del servidor de bases de datos MySQL. Este proceso puede realizarse de forma muy sencilla y rápida utilizando el paquete de software XAMPP. Además incorpora algunas herramientas para poder administrar estos servidores muy fácilmente. No obstante, usar un servidor de hosting comercial puede ser una alternativa más sencilla y segura. Si no estás interesado en instalar tu propio servidor web, puedes saltarte este ejercicio y realizar el ejercicio que encontrarás más adelante.

1.    Descarga la última versión de XAMPP de la siguiente web:

http://www.apachefriends.org/en/xampp.html

 

Encontrarás versiones para Linux, Windows y Mac. En este ejercicio hemos instalado la versión 1.8.2 para Windows usando el ejecutable.

2.     Inicia el proceso de instalación según tu SO. Los componentes mínimos necesarios para este ejercicio se indican a continuación:

3.    Una vez instalado ejecuta XAMPP Control Panel:

4.     Pulsa los botones Start, tanto para Apache como para MySQL.

5.     Para verificar que el servidor web está en marcha, abre un navegador y desde la barra de direcciones accede a http://localhost. Se mostrará una página con información sobre XAMPP.

Ejercicio: Configuración de Apache

En este ejercicio veremos una visión superficial sobre la configuración del servidor web Apache.

1.     Verifica que el servidor está arrancado accediendo a la dirección http://localhost. Se utiliza para referirte a tu propia dirección IP. Es equivalente a escribir http://127.0.0.1.

2.     Dentro de la carpeta donde hayas instalado XAMPP (por ejemplo, C:/xampp) abre la carpeta htdocs. Dentro encontrarás todos los ficheros que publica el servidor.

3.     El fichero que toma por defecto en esta carpeta es index.php (o index.html si no lo encuentra). Edita este fichero y estudia su estructura. Modifica este fichero y recarga en el navegador para observar los cambios (http://localhost).

4.     Ejecuta XAMPP Control Panel y pulsa el botón Config de Apache. Selecciona Apache (httpd.conf). Se editará el fichero xampp/apache/conf/httpd.conf. Es un fichero bastante largo, aunque normalmente solo tendrás que modificar los siguientes parámetros:

  • Listen 80 - Indica que el servidor escucha el puerto 80. Es frecuente cambiar este valor por 8080 o 888. Para aceptar conexiones solo de este host, cambia la línea por Listen 127.0.0.1:80.
  • ServerAdmin postmaster@localhost - Correo electrónico del administrador del servidor.
  • ServerName localhost:80 - Nombre del servidor, indicando nombre de dominio y puerto. Si no se indica, trata de obtenerlo automáticamente.
  • DocumentRoot "C:/xampp/htdocs" - El directorio donde se encuentran los documentos servidos por el servidor.
  •  DirectoryIndex index.php index.pl … index.html - Establece el archivo que Apache ofrece cuando se solicita un directorio sin indicar un archivo concreto. De encontrar varios se escoge el que esté antes en esta lista.

Si modificas algún valor, recuerda guardar el fichero y reinicializar Apache para que se carguen los nuevos valores.

5.     Vamos a probar si el servidor web es accesible desde otros dispositivos conectados a tu red de área local. Utiliza el comando ipconfig (Windows) o ifconfig (Linux/Mac) para averiguar la dirección IP de tu ordenador.

6.     Abre un navegador desde otro dispositivo y accede a la dirección que acabas de obtener. Si lo haces desde un móvil, has de acceder a través de Wi-Fi. Si tienes problemas, es posible que sea culpa del cortafuegos. En este caso tendrás que desactivarlo.

Ejercicio: Un servicio web con PHP y MySQL

En este ejercicio comenzamos creando una base de datos y luego escribiremos un par de ficheros PHP que implementarán las dos acciones del servicio web puntuaciones.

1.    Ejecuta XAMPP Control Panel y asegúrate de que tanto Apache como MySQL están arrancados.

2.     Pulsa el botón Admin de MySQL que encontrarás en XAMPP Control Panel. De esta forma accedemos a la herramienta de administración del servidor de bases de datos phpMyAdmin.

3.     Selecciona la lengüeta SQL e introduce las siguiente instrucciones en el cuadro de texto:

CREATE DATABASE IF NOT EXISTS puntuaciones;
USE puntuaciones;

CREATE TABLE puntuaciones (
  _id INTEGER PRIMARY KEY AUTO_INCREMENT,
  puntos INTEGER, nombre TEXT, fecha BIGINT);

INSERT INTO puntuaciones (puntos, nombre, fecha) VALUES
  (10000, 'Pedro', 0),
  (20000, 'Rosa', 0);

4.     Pulsa el botón OK para ejecutar estas sentencias.

5.     En el marco de la izquierda, pulsa el botón verde con forma de recargar. Observa como en la lista de bases de datos aparece puntuaciones. Si pulsas en el botón + de su izquierda se mostrarán sus tablas.

6.     Selecciona la tabla puntuaciones para examinar su contenido.

Como puedes observar, disponemos de diferentes herramientas para editar los valores de la tabla. Por ejemplo, podemos utiliza la lengüeta Insertar para añadir una nueva fila a la tabla.

7.     Explora otras utilidades que nos ofrece phpMyAdmin para trabajar con bases de datos.

8.     Dentro de la carpeta donde hayas instalado XAMPP (por ejemplo, C:/xampp), abre la carpeta htdocs. Crea dentro la carpeta puntuaciones.

9.    Dentro de esta carpeta crea el fichero lista.php con el siguiente contenido:

<?php
   $con = new mysqli('localhost', 'root', '', 'puntuaciones');
   if ($con->connect_errno) {
      echo 'Error al conectar base de datos: ', $con->connect_error;
      exit();
   }

   $sql = 'SELECT puntos, nombre FROM puntuaciones ORDER BY fecha DESC';

   if (isset($_GET['max'])) {
      $sql .= ' LIMIT ?';
   }

   $cursor = $con->stmt_init();
   if ($cursor->prepare($sql)) {
      if (isset($_GET['max'])) {
         $cursor->bind_param("s",$_GET['max']);
      }       
      $cursor->execute();
      $cursor->bind_result($puntos, $nombre);
      while($cursor->fetch()) {
         echo $puntos.' '.$nombre."\n";
      }
      echo "\n";
      $cursor->close();
   }
   $con->close();
?>

El código PHP suele estar entremezclado entre el código HTML. Para diferenciarlo de este, hay que introducirlo entre los caracteres <?php y ?>. La primera sentencia establece una conexión a una base de datos situada en un servidor MySQL. Necesita cuatro parámetros: primero, la dirección IP donde está el servidor (cuando se indica localhost nos referimos a nuestra propia IP); luego, usuario y contraseña usados en la conexión (en el ejemplo, usuario root y sin contraseña; sería muy conveniente introducir otros valores en un caso real); finalmente, el nombre de la base de datos a utilizar. La conexión se guarda en el objeto $con. Observa como las variables en PHP siempre comienzan con el carácter $, además no han de declararse.

En la siguiente línea se accede a una propiedad del objeto $con para verificar si ha habido algún error. Observa como para aceder a las propiedades de un objeto en PHP se utilizan los caracteres -> en lugar del carácter . usado en Java. Luego se configura la codificación de caracteres y se inicializa la variable $sql con la consulta a realizar. Solo nos interesa puntos y nombre de la tabla puntuaciones ordenados por fecha. Para concatenar dos cadenas en PHP se utiliza el carácter punto (.).

En el siguiente if verificamos si nos han pasado el parámetro max a través de la URL. Por ejemplo:

http://localhost/puntuaciones/lista.php?max=10

En caso de que el array $_GET[ ] contenga este parámetro, añadimos a la consulta SQL una restricción en el número de parámetros devueltos.

A continuación preparamos y ejecutamos la consulta SQL que se recogerá en la variable $cursor. Utilizando el método bind_result() asociamos los dos campos indicados en la cláusula SELECT con dos variables PHP. Luego recorremos todos los elementos de $cursor y por cada uno devolvemos una línea de texto plano con los puntos, el nombre y un salto de línea. Más adelante intentaremos devolverlo en un formato XML. Terminamos cerrando el cursor y la conexión.

10.     Abre un navegador web y escribe la siguiente dirección:

http://localhost/puntuaciones/lista.php?max=10

11.      Es posible que el resultado se muestre en una sola línea. El navegador espera como resultado de la consulta una página HTML, y no hemos introducido en el resultados la etiqueta <br/> tras cada línea. Selecciona la opción Ver código fuente de la página para ver el resultado correctamente.

12.      Crea el fichero nueva.php en la misma carpeta con el siguiente código:

 <?php
   $con = new mysqli('localhost', 'root', '', 'puntuaciones');
   if ($con->connect_errno) {
      echo 'Error al conectar base de datos: ', $con->connect_error;
      exit();
   }
   $puntos = $_GET['puntos'];
   $nombre = htmlspecialchars($_GET['nombre']);
   $fecha  = $_GET['fecha'];   
   $sql = $con->prepare('INSERT INTO puntuaciones VALUES (null,?, ?, ?)');
   $sql->bind_param('isi', $puntos, $nombre, $fecha);
   $sql->execute();
   echo 'OK\n';
   $con->close();
?>

 

13.     Puedes comprobar su funcionamiento accediendo a la siguiente dirección:

http://localhost/puntuaciones/nueva.php?puntos=3000&nombre=María&fecha=20

14.      Verifica que el nuevo elemento ha sido añadido. Puedes usar la URL:

http://localhost/puntuaciones/lista.php?max=10

 

 

 

- Utilizando el servicio web PHP desde Asteroides

En este apartado vamos a realizar un cliente del servicio web diseñado en el apartado anterior para usarlo desde Asteroides.

Ejercicio: Uso del servicio web PHP en Asteroides

En este ejercicio comenzamos creando una base de datos y luego escribiremos un par de ficheros PHP que implementarán las dos acciones del servicio web puntuaciones.

1.    Abre el proyecto Asteroides.

2.    Vamos a hacer el acceso a la red desde el hilo principal. Para evitar que StrictMode nos lo impida, añade el siguiente código en el método onCreatede MainActivity.java:

StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder().
                                           permitNetwork().build());

3.    Para poder acceder a StrictMode tenemos un nivel de API mínimo de 9. Abre AndroidManifest.xml y asegúrate de que el valor de minSdkVersion sea igual o mayor que 9:

<uses-sdk android:minSdkVersion="9"

4.    Como en todos los ejemplos de este tema, asegúrate de que la aplicación solicita el permiso de acceso a Internet. Añade la siguiente línea en AndroidManifest.xml:

<uses-permission android:name="android.permission.INTERNET"/>

5.    Pasemos a implementar la interfaz AlmacenPuntuacionesaccediendo al servidor de servicios web que acabamos de desarrollar. Para ello crea una nueva clase en la aplicación Asteroides y copia el siguiente código:

public class AlmacenPuntuacionesSW_PHP implements AlmacenPuntuaciones {

   public Vector<String> listaPuntuaciones(int cantidad) {
      Vector<String> result = new Vector<String>();
      try {
         URL url=new URL("http://158.42.146.127/puntuaciones/lista.php"
                                                             + "?max=20");
         HttpURLConnection conexion = (HttpURLConnection) url
                                                        .openConnection();
         if (conexion.getResponseCode() == HttpURLConnection.HTTP_OK) {
            BufferedReader reader = new BufferedReader(new
               InputStreamReader(conexion.getInputStream()));
            String linea = reader.readLine();
            while (!linea.equals("")) {
               result.add(linea);
               linea = reader.readLine();
            }
            reader.close();
         } else {
            Log.e("Asteroides", conexion.getResponseMessage());
         }
      } catch (Exception e) {
         Log.e("Asteroides", e.getMessage(), e);
      } finally {
         if (conexion!=null) conexion.disconnect();
         return result;
      }
   }

El primer método se encarga de invocar la operación lista.php del servicio web que acabamos de implementar. Comienza definiendo la URL correspondiente al servicio web. En el código hay que reemplazar 158.42.146.127 por la dirección IP de tu ordenador. Recuerda que este programa lo ejecutarás desde el emulador o desde un teléfono real, y en ambos casos la IP será diferente de la de tu ordenador. Esto imposibilita utilizar como dirección localhost, como sí hicimos con otros clientes que ejecutábamos desde el mismo ordenador.

Una vez creada la URL se establece la conexión y mediante el método GET se manda el parámetro correspondiente.

6.    Pasemos a ver el segundo método de la clase. A continuación copia el siguiente código:

public void guardarPuntuacion(int puntos, String nombre, long fecha) {
      try {
         URL url=new URL("http://158.42.146.127/puntuaciones/nueva.php?"
               + "puntos="+ puntos
               + "&nombre="+ URLEncoder.encode(nombre, "UTF-8")
               + "&fecha=" + fecha);
         HttpURLConnection conexion = (HttpURLConnection) url
                                                .openConnection();
         if (conexion.getResponseCode() == HttpURLConnection.HTTP_OK) {
            BufferedReader reader = new BufferedReader(new
                  InputStreamReader(conexion.getInputStream()));
            String linea = reader.readLine();
            if (!linea.equals("OK")) {
               Log.e("Asteroides","Error en servicio Web nueva");
            }
         } else {
            Log.e("Asteroides", conexion.getResponseMessage());
         }
      } catch (Exception e) {
         Log.e("Asteroides", e.getMessage(), e);
      } finally {
         if (conexion!=null) conexion.disconnect();
      }
   }
}

La estructura de este método es similar al anterior, pero ahora llamamos a la operación nueva.php. Recuerda que en caso de una llamada satisfactoria, la respuesta ha de ser OK. Consideraremos que ha habido un error si esta no es la respuesta.

7.    Puedes reemplazar 158.42.146.127 por la dirección IP de tu ordenador.

8.    Modifica el código correspondiente para que la nueva clase pueda ser seleccionada como almacén de las puntuaciones.

9.    Verifica el funcionamiento.

 

- Creación de un servicio web en un servidor de hosting

Existen muchas empresas que ofrecen servicio de hosting, algunas incluso de forma gratuita. Por lo general, suele ser una solución más sencilla, fiable y barata que mantener nuestros propios servidores.

En este apartado aprenderemos a montar un servicio web en uno de estos servidores. Hemos elegido la empresa Hostinazo porque ofrece uno de los mejores paquetes gratuitos. Aunque en la actualidad esta empresa incrusta propaganda en su servicio gratuito, podemos encontrar otras empresas que no la incrustan. Este inconveniente podrá evitarse, dado que usaremos el hosting para nuestro servicio web y la propaganda incrustada no será vista por nuestros usuarios.

Los pasos del siguiente ejercicio se han preparado para el hosting Hostinger. No obstante, estos pasos son muy similares para otras empresas de hosting.

Ejercicio: Uso del servicio web en un servidor de hosting

En este ejercicio comenzamos creando una base de datos y luego escribiremos un par de ficheros PHP que implementarán las dos acciones del servicio web puntuaciones.

1.    Accede a la página http://www.hostinger.es. Busca la oferta  “Hosting gratis”-

2.    Rellena el formulario de registro.

3.    Una vez completado, te enviarán un correo para activar tu cuenta. Entra en tu correo y pulsa sobre el enlace que te indican. Si no recibes ningún correo, verifica la carpeta de spam.

4.     Con tu usuario activado podrás crear un nuevo plan de hosting gratuito. Te pedirá que indiques si quieres usar tu propio dominio o usar un subdominio. Selecciona esta última opción e introduce el subdominio que prefieras. También te pedirá una nueva contraseña asociada a este hosting.

En este ejercicio hemos usado el subdominio jtomas.esy.es. Tendrás que seleccionar uno diferente y acordarte de reemplazarlo en el resto del ejercicio.

5.    Pasados unos minutos, ya dispondrás de tu cuenta. Refresca el navegador hasta que se muestre la siguiente información:

6.    Introduce en un navegador tu dominio para ver que ya funciona. Por supuesto, podrás poner tu propio contenido.

7.    Pulsa el botón Administrar para gestionar tu cuenta. Dispondrás de decenas de herramientas para gestionar diferentes aspectos de tu hosting. En la sección Bases de Datos encontrarás las siguientes opciones:

8.   Selecciona la opción Bases de Datos MySQL y crea una nueva base de datos. Te pedirá que completes los siguientes datos:

Apunta estos datos, los vas a necesitar más tarde. Has de saber que este hosting gratuito solo te permite una base de datos. Pulsa Create.

9.    Regresa a cPanel. Para ello pulsa el nombre de la cuenta en la barra de navegación .

Selecciona la herramienta phpMyAdmin. Te mostrará una lista con tus bases de datos. Pulsa sobre el enlace Ingresar phpMyAdmin.

10.   Esta herramienta de gestión de bases de datos ya la utilizamos cuando instalamos el servidor en local. Para crear la tabla puntuaciones selecciona la lengüeta SQL e introduce las siguiente instrucciones en el cuadro de texto:

CREATE TABLE puntuaciones (
  _id INTEGER PRIMARY KEY AUTO_INCREMENT,
  puntos INTEGER, nombre TEXT, fecha BIGINT);
INSERT INTO puntuaciones (puntos, nombre, fecha) VALUES
  (10000, 'Pedro', 0),
  (20000, 'Rosa', 0);

11.   Pulsa el botón Continuar para ejecutar estas sentencias SQL.

12.   Recarga la página y observa como en la lista de bases de datos de la izquierda aparece la tabla Puntuaciones. Si pulsas sobre esta, podrás visualizar y editar su contenido.

13.   Regresa a Administrar y en la sección Archivos selecciona Administrador de Archivos 2. Esta herramienta se situará en la raíz de tu almacenamiento.

14.    Entra en la carpeta public_html, pulsa el botón New dir e introduce en el primer recuadro de texto puntuaciones. Entra ahora en la carpeta puntuaciones.

15.    Pulsa el botón Upload y selecciona en tu ordenador el fichero lista.php creado en el ejercicio “Un servicio web con PHP y MySQL”.

16.   Sube también el fichero nueva.php creado en este mismo ejercicio.

17.   Selecciona el archivos lista.php y pulsa el botón Editar archivos.

18.   Modifica la primera línea con los datos adecuados para la base de datos creada en el punto 7. En el ejemplo mostrado sería:

$con = new mysqli('mysql.hostinger.es', 'u449064073_punt',  
      'mi_password', 'u449064073_punt');

Estos cuatro parámetros corresponden al servidor MySQL que contiene la base de datos, usuario con su password y base de datos a la que nos conectaremos.

19.   Modifica la primera línea de nueva.php con la misma información.

20.   Ya tenemos nuestro servicio web creado. Para probarlo, introduce la siguiente dirección en un navegador, reemplazando el subdominio:

http://jtomas.esy.es/puntuaciones/lista.php

21.   Utiliza la opción Ver código fuente de la página. Se mostrará algo similar a:

10000 Pedro
20000 Rosa

22.   Utiliza la siguiente URL para verificar la inserción de datos:

http://jtomas.esy.es/puntuaciones/nueva.php?puntos=30000&nombre=Mar +Antonia&fecha=20

23.   Abre el proyecto Asteroides. Edita AlmacenPuntuacionesSW_PHP.java y reemplaza las dos direcciones IP utilizadas por tu subdominio (jtomas.esy.es).

24.   Ejecuta la aplicación y verifica que las puntuaciones son almacenadas en nuestro servidor de hosting.

Las ventajas de un servicio de hosting frente a un servidor propio son múltiples: el mantenimiento del servidor y su monitorización, la seguridad y las copias de seguridad dejan de ser responsabilidad nuestra. Además, el tráfico de Internet ya no ha de pasar por nuestra red. Y todo esto a precios muy reducidos o incluso gratis.

 

- Utilizando AsyncTask de forma síncrona

Como hemos visto en este capítulo, Android impide que las operaciones con la red se realicen desde el hilo de la interfaz de usuario. Para saltarnos esta prohibición hemos desactivado StrictMode. Aunque lo más recomendable sería lanzar tareas asíncronas, en otros hilos, para acceder a la red.

Cuando intentamos aplicar esta segunda alternativa en Asteroides, aparece un problema: el método AlmacenPuntuaciones.listaPuntuaciones() ha sido diseñado para un uso síncrono; es decir, cuando se llama hay que esperar hasta que nos devuelvan el resultado, no siendo posible que el método vuelva inmediatamente, con lo que se deja la tarea pendiente. Posiblemente, habría sido interesante diseñar esta interfaz para trabajar de forma asíncrona. Por ejemplo, añadiendo el método comienzaDescargaPuntuaciones(), que arranca una tarea para la descarga y devuelve inmediatamente el control sin devolver nada. Desde esta tarea se podría lanzar un evento cuando se dispusiera de las puntuaciones.

En este ejercicio no vamos a cambiar el diseño de esta interfaz. En lugar de ello, vamos a introducir un AsyncTask dentro de listaPuntuaciones() y no retornaremos de este método hasta que termine y ya tengamos las puntuaciones. El siguiente esquema muestra este planteamiento:

 

Trabajando de esta manera realizamos el acceso a la red desde un hilo secundario. Por lo tanto, StrictMode no se quejará. Pero es muy importante que entiendas que realmente no hemos resuelto nada. Esta forma de trabajar bloquea igualmente el hilo de la interfaz de usuario. Es decir, un método execute() seguido de un get() es equivalente a llamar la tarea de una forma síncrona.

Entonces, ¿qué ventaja tiene usar un AsyncTask? En el método get() vamos a poder fijar un tiempo máximo a la tarea, por ejemplo get(4, TimeUnit.SECONDS). Pasado este tiempo, informamos al usuario de que el servidor no responde y continuamos.

Ejercicio: Uso síncrono de AsyncTask para acceso al servicio web PHP
              de puntuaciones

1.    En el proyecto Asteroides, copia la clase AlmacenPuntuacionesSW_PHP en una nueva clase y llámala AlmacenPuntuacionesSW_PHP_AsyncTask.

2.   Introduce las siguientes líneas al comienzo de la nueva clase, reemplazando el método listaPuntuaciones():

private Context contexto;

public AlmacenPuntuacionesSW_PHP_AsyncTask(Context contexto) {
   this.contexto = contexto;
}

public Vector<String> listaPuntuaciones(int cantidad) {
   try {
      TareaLista tarea = new TareaLista();
      tarea.execute(cantidad);
      return tarea.get(4, TimeUnit.SECONDS);
   } catch (TimeoutException e) {
      Toast.makeText(contexto, "Tiempo excedido al conectar",
            Toast.LENGTH_LONG).show();
   } catch (CancellationException e) {
      Toast.makeText(contexto, "Error al conectar con servidor",
            Toast.LENGTH_LONG).show();
   } catch (Exception e) {
      Toast.makeText(contexto, "Error con tarea asíncrona",
            Toast.LENGTH_LONG).show();
   }
   return new Vector<String>();
}

private class TareaLista extends AsyncTask<Integer, Void, Vector<String>>{
   @Override
   protected Vector<String> doInBackground(Integer... cantidad){
      //Copia el código que antes estaba en listaPuntuaciones()
   }
}

Empezamos añadiendo un constructor a la clase para indicar el contexto donde se ejecuta. Esto nos permitirá introducir Toast() en la clase.

Para obtener la lista de puntuaciones desde un nuevo hilo se ha creado un descendiente de AsyncTask que toma como parámetro de entrada un entero con la cantidad máxima de puntuaciones a obtener y nos devuelve un vector de String. El código de la tarea a realizar es casi idéntico al usado en el ejercicio anterior. Por esta razón no se ha incluido. En el siguiente punto se indica lo único que tendrás que cambiar. Para usar esta nueva clase se ha instanciado el objeto tarea.

En listaPuntuaciones() comenzamos instanciando un objeto de la clase TareaLista. El método execute() es utilizado para pasar los parámetros de entrada y arrancar la tarea. El método get() espera a que la tarea concluya y nos devuelve su salida. Como se ha comentado en el capítulo 5, hay que usar este método con cuidado, dado que bloquea el hilo de la interfaz de usuario. Sin embargo, dado que mientras estamos esperando la respuesta del servidor el usuario no puede realizar ninguna interacción, no va a suponer ningún problema. Para asegurarnos de que no nos quedamos bloqueados un tiempo excesivo, usamos una de las sobrecargas del método get(), que nos permite indicar el tiempo máximo a esperar y la unidad en que medimos este tiempo. En caso de sobrepasar este tiempo, se generará una excepción TimeoutException, que se procesa en la sección catch. También se recoge la posibilidad de que ocurra una excepción de cancelación de tarea. Al final del ejercicio se añade el método adecuado para cancelar si ocurre algún tipo de error.

3.   Reemplaza el código + "?max="+cantidad); por + "?max="+cantidad[0]);. Aunque esta tarea solo necesita un entero, la mecánica de AsyncTask hace que se nos pase un array de enteros.

4.   Introduce las siguientes líneas, reemplazando el método guardarPuntuacion():

public void guardarPuntuacion(int puntos, String nombre, long fecha){
   try {
      TareaGuardar tarea = new TareaGuardar();
      tarea.execute(String.valueOf(puntos), nombre,
            String.valueOf(fecha));
      tarea.get(4, TimeUnit.SECONDS);
   } catch (TimeoutException e) {
      Toast.makeText(contexto, "Tiempo excedido al conectar",
            Toast.LENGTH_LONG).show();
   } catch (CancellationException e) {
      Toast.makeText(contexto, "Error al conectar con servidor",
            Toast.LENGTH_LONG).show();
   } catch (Exception e) {
      Toast.makeText(contexto, "Error con tarea asíncrona",
            Toast.LENGTH_LONG).show();
   }
}

private class TareaGuardar extends AsyncTask<String, Void, Void> {
   @Override
   protected Void doInBackground(String... param) {
      try {
         URL url = new URL(
                  "http://jtomas.hostinazo.com/puntuaciones/nueva.php"
                  + "?puntos=" + param[0] + "&nombre="
                  + URLEncoder.encode(param[1], "UTF-8")
                  + "&fecha=" + param[2]);
         //Copia el código que antes estaba en guardarPuntuaciones
         return null;
   }
}

La mecánica para llamar al servicio web que almacena una nueva puntuación es similar al anterior. La única diferencia está en las clases que parametriza el AsyncTask. Ahora, como entrada, hay que introducir tres strings: puntos, nombre y fecha de la puntuación. Además, la tarea no nos devuelve ninguna información.

5.    Busca en la clase las apariciones de Log.e(…); y añade en la fila inferior cancel(true);. En total tienes que añadir 5 líneas. Con esto indicamos que queremos que se cancele la tarea en caso de error.

6.   Modifica la clase MainActivity.java y las res/values/arrays.xml para que el nuevo tipo de almacenamiento pueda ser seleccionado.

7.   Modifica el código correspondiente para que la nueva clase pueda ser seleccionada como almacén de las puntuaciones.

8.   Verifica el funcionamiento.

9.   Para verificar que su comportamiento es robusto ante errores en la red, desconecta el acceso a Internet del dispositivo y verifica que al listar las puntuaciones te indica: “Error al conectar con servidor”. Prueba a introducir la llamada sleep(5) en el fichero lista.php del servidor. Con esto se añade un retardo de 5 segundos en la respuesta. Verifica que al listar las puntuaciones te indica: “Tiempo excedido al conectar”.