Tutoriales Android

Introducción

Los teléfonos Android suelen disponer de conexión a Internet. Esto nos permite no solo almacenar los datos en nuestro dispositivo, si no compartirlos con otros usuarios. En el primer punto del capítulo trataremos de resolver el problema de comunicar dos aplicaciones en Internet mediante la herramienta básica: los sockets. Existen otras alternativas de más alto nivel, como el uso del protocolo HTTP, que será estudiada en el segundo punto del capítulo. Para terminar se tratará una tercera alternativa, todavía de más alto nivel, los servicios web.

En este capítulo implementáramos el mismo ejemplo que en el capítulo anterior, es decir, almacenaremos las puntuaciones obtenidas en Asteroides, pero ahora en un servidor de Internet. Utilizaremos las tres alternativas descritas en el párrafo anterior. No obstante, has de tener claro que estos mecanismos están relacionados entre sí. Por ejemplo, si utilizas servicios web, internamente se utilizará el protocolo HTTP y además este protocolo utiliza sockets para establecer la comunicación.

Objetivos:

  • Repasar las alternativas principales para intercambiar datos por Internet.
  • Describir el uso de socket como herramienta básica de una aplicación para comunicarse con otras aplicaciones por Internet.
  • Mostrar cómo se programaría un protocolo basado en sockets sobre TCP.
  • Describir el funcionamiento del protocolo HTTP
  • Mostrar cómo se pueden programa peticiones HTTP desde Android.
  • Definir el concepto de servicio Web y comparar las alternativas más importantes.
  • Mostrar el acceso a servicios web de terceros desde un cliente Android.
  • Aprender a crear nuestro propio servidor de servicios web con PHP, Apache y MySQL.

Comunicaciones en Internet mediante sockets

Antes de definir que es un socket conviene aclarar los roles o configuraciones que pueden tomar las aplicaciones en un proceso de comunicación. Las dos configuraciones más importantes que pueden tomar son: las llamadas “arquitectura igual a igual” y la “arquitectura cliente/servidor”. Esta segunda es la que utilizaremos en los ejemplos de este capítulo, por tanto, conviene aclarar este aspecto.

La arquitectura cliente/servidor

Las aplicaciones en Internet suelen seguir la arquitectura cliente/servidor. Esta arquitectura se caracteriza por descomponer el trabajo en dos partes (es decir dos programas): el servidor, que centraliza el servicio, y el cliente, que controla la interacción con el usuario. El servidor ha de ofrecer sus servicios a través de una dirección conocida. Algunos ejemplos de aplicaciones basadas en la arquitectura cliente/servidor son WWW o el correo electrónico. Se suelen seguir las siguientes pautas de comportamiento en esta arquitectura:

- ¿Qué es un socket?

Cada una de las diferentes aplicaciones en Internet (web, correo electrónico,…) ha de poder intercambiar información entre programas situados en diferentes ordenadores o dispositivos. Con este propósito, se va ha hacer uso del nivel de transporte de la pila de protocolos TCP/IP, cuyo objetivo final es permitir el intercambio de información a través de la red de forma fiable y transparente.

video[Tutorial] El interfaz socket

La interfaz socket define las reglas que un programa ha de seguir para utilizar los servicios del nivel de transporte en una red TCP/IP. Esta interfaz se basa en el concepto de socket. Un socket es el punto final de una comunicación bidireccional entre dos programas que intercambian información a través de Internet (socket se traduce literalmente como “enchufe”).

Dado que en un mismo dispositivo/ordenador podemos estar ejecutando de forma simultánea diferentes aplicaciones que utilizan Internet para comunicarse, resulta imprescindible identificar cada socket con una dirección diferente. Un socket se va a identificar por la dirección IP del dispositivo, más un número de puerto (de 16 bits). En Internet se suele asociar a cada aplicación un número de puerto concreto (por ejemplo: 80 para la web, 25 para el correo electrónico, 7 para ECHO o 4661 para eDonkey).

Una conexión está determinada por un par de sockets, uno en cada extremo de la conexión. Existen dos tipos de socket: socket stream y socket datagram. Veamos en qué se diferencian:

- Sockets stream (TCP)-datagram (UDP)

Sockets stream (TCP)

Los sockets stream ofrecen un servicio orientado a conexión, donde los datos se transfieren como un flujo continuo, sin encuadrarlos en registros o bloques. Este tipo de socket se basa en el protocolo TCP, que es un protocolo orientado a conexión. Esto implica que antes de transmitir información hay que establecer una conexión entre los dos sockets. Mientras uno de los socketsatiende peticiones de conexión (servidor), el otro solicita la conexión (cliente). Una vez que los dossockets están conectados, ya se puede transmitir datos en ambas direcciones. El protocolo incorpora de forma transparente al programador la corrección de errores. Es decir, si detecta que parte de la información no llegó a su destino correctamente, esta volverá a ser trasmitida. Además, no limita el tamaño máximo de información a transmitir.

Sockets datagram (UDP)

Los sockets datagram se basan en el protocolo UDP y ofrecen un servicio de transporte sin conexión. Es decir, podemos mandar información a un destino sin necesidad de realizar una conexión previa. El protocolo UDP es más eficiente que el TCP, pero tiene el inconveniente de que no se garantiza la fiabilidad. Además, los datos se envían y reciben en datagramas (paquetes de información) de tamaño limitado. La entrega de un datagrama no está garantizada: estos pueden duplicarse, perderse o llegar en un orden diferente del que se envió.

La gran ventaja de este tipo de sockets es que apenas introducen sobrecarga sobre la información transmitida. Además, los retrasos introducidos son mínimos, lo cual los hace especialmente interesantes para aplicaciones en tiempo real, como la transmisión de audio y vídeo sobre Internet. Sin embargo, presentan muchos inconvenientes para el programador: cuando transmitimos un datagrama no tenemos la certeza de que este llegue a su destino, por lo que, si fuera necesario, tendríamos que implementar nuestro propio mecanismo de control de errores. Otro inconveniente es el hecho de que existe un tamaño máximo de datagrama: unos 1500 bytes dependiendo de la implementación. Si la información a enviar es mayor, tendremos que fraccionarla y enviar varios datagramas independientes. En el destino tendremos que concatenarlos en el orden correcto.

En conclusión, si deseas una comunicación libre de errores y sin preocupaciones para el programador, es más conveniente que utilices sockets stream. Es el tipo de sockets que utilizaremos en los siguientes ejemplos.

 

- Un ejemplo de un cliente / servidor de ECHO

 

El servicio ECHO suele estar instalado en el puerto 7 de máquinas Unix y permite comprobar que la máquina está operativa y que se puede establecer una conexión con dicha máquina.

El funcionamiento de un servidor ECHO es muy sencillo, cuando alguien se conecta espera que le envíe algo y le responde exactamente con la misma información recibida. El cliente actúa de forma contraria; envía datos al servidor y luego comprueba que los datos recibidos son idénticos a los transmitidos.

Ejercicio: Estudio del protocolo ECHO con el comando telnet.

El siguiente ejercicio nos muestra un truco para testear si un servidor que utiliza TCP funciona correctamente. Te recomendamos que lo utilices antes de realizar la programación del cliente. Por una parte, te permitirá asegurarte que tanto la conexión, como el servidor funcionan correctamente. Por otra parte, te asegurarás que has entendido correctamente el protocolo a implementar.

NOTA: Para que el ejemplo funcione en la dirección IP 158.42.146.127 ha de haber un servidor de ECHO en funcionamiento. En caso de no ser así, puedes reemplazar la IP por la de un servidor de ECHO en funcionamiento. También puedes implementar tu propio servidor de ECHO,  tal y como se muestra en uno de los siguientes ejercicios.

1.     Para verificar si el servidor de ECHO está en marcha,  desde un intérprete de comandos (símbolo del sistema/shell) escribe:

  telnet 158.42.146.127 7

Este comando permite establecer una conexión TCP con el puerto 7 del servidor. A partir de ahora todo lo que escribas se enviará al servidor (aunque en muchos clientes Telnet no se muestra en pantalla lo que escribes) y todo lo que el servidor envíe se imprimirá en pantalla.

 

NOTA: Windows 7 tiene desactivado por defecto el comando Telnet. Para habilitarlo, haz clic en el menú Inicio > Panel de control > Programas > Activar o desactiva las características de Windows y marca Cliente Telnet.

 

2.     Espera a que se establezca la conexión. De no ser posible, vuelve a intentarlo o lee la nota del principio del ejercicio.

3.     Escribe una frase cualquiera y pulsa <Intro>. Por ejemplo:

Hola hola

Si te equivocas no uses la tecla de borrar. En tal caso, repite el ejercicio desde el punto 3. Equivocarse en este protocolo no tiene ninguna repercusión, pero en el resto que vamos a estudiar hará que el servidor no nos entienda.

4.     Observa que la respuesta obtenida coincide con la frase que has introducido. Además el servidor cerrará inmediatamente la conexión y no te permitirá mandar más frases.

Ejercicio: Un cliente de ECHO

El siguiente ejemplo muestra cómo podrías desarrollar un cliente de ECHO que utiliza unsocket stream desde Android.

NOTA: Para que el ejemplo funcione en la dirección IP 158.42.146.127 ha de haber un servidor de ECHO en funcionamiento. En caso de no ser así, puedes reemplazar la IP por la de un servidor de ECHO en funcionamiento o realizar el siguiente ejercicio.

1.     Crea una nueva aplicación con los siguientes datos:

Application Name: Cliente ECHO

     Package Name: org.example.clienteecho

☑Phone and Tablet

Minimum SDK: API 9 Android 2.3 (Gingerbread)

Add an activity: Empty Activity

 

NOTA: Utilizamos como versión mínima la 2.3 para poder configurar StrictMode (se explica a continuación).

2.     Añade la etiqueta android:id="@+id/TextView01" en el TextView del layout de la actividad.

3.     Reemplaza el código de la actividad por:

public class MainActivity extends Activity {
   private TextView output;

   @Override
   public void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      setContentView(R.layout.activity_main);
      output = (TextView) findViewById(R.id.TextView01);
      StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
            .permitNetwork().build());
      ejecutaCliente();
   }

   private void ejecutaCliente() {
      String ip = "158.42.146.127";
      int puerto = 7;
      log(" socket " + ip + " " + puerto);
      try {
         Socket sk = new Socket(ip, puerto);
         BufferedReader entrada = new BufferedReader(
                  new InputStreamReader(sk.getInputStream()));
         PrintWriter salida = new PrintWriter(
                  new OutputStreamWriter(sk.getOutputStream()), true);
         log("enviando... Hola Mundo ");
         salida.println("Hola Mundo");
         log("recibiendo ... " + entrada.readLine());
         sk.close();
      } catch (Exception e) {
         log("error: " + e.toString());
      }
   }

   private void log(String string) {
      output.append(string + "\n");
   }
}


Las tres primeras líneas del método onCreate() han de resultarte familiares. En la cuarta se configura StrictMode[1]. Consiste en una herramienta de desarrollo que detecta cosas que podrías estar haciendo mal y te llama la atención para que las corrijas. Esta herramienta aparece en el nivel de API 9. Una de las verificaciones que realiza es que no se acceda a la red desde el hilo principal. Este problema se podría resolver lanzando un nuevo hilo para realizar el acceso al servidor[2]. Más adelante se muestra como resolverlo usando AsyncTask. En este apartado queremos centrarnos en el uso de sockets y no en el manejo de hilos, por lo que vamos a desactivar esta comprobación. En la línea en cuestión se indica a StrictMode que en su política dethreads no tenga en cuenta los accesos a la red.

La parte interesante se encuentra en el método ejecutarCliente(). En primer lugar, todo cliente ha de conocer la dirección del socket del servidor; en este caso los valores se indican en el par de variables ip y puerto. Nunca tenemos la certeza de que el servidor admita la conexión, por lo que es obligatorio utilizar una sección try / catch. La conexión propiamente dicha se realiza con el constructor de la clase Socket. Siempre hay que tener previsto que ocurra algún problema, en tal caso, se creará la excepción y se pasará a la sección catch. En caso contrario, continuaremos obteniendo el InputStream y el OutputStream asociado alsocket. Lo cual nos permitirá obtener las variables, entrada y salida mediante las que podremos recibir y transmitir información. El programa transmite la cadena "Hola Mundo", tras lo que visualiza la información recibida. Si todo es correcto, ha de coincidir con lo transmitido.

 

4.     Solicita en la aplicación el permiso INTERNET en AndroidManifest.

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

5.     Ejecuta la aplicación y verifica si el servidor responde. Recuerda que es posible que no se haya arrancado este servicio en el servidor.

6.   Comenta la línea de onCreate() donde se se configura StrictMode y ejecuta de nuevo la aplicación. El resultado no será satisfactorio. En este caso ocurrirá una excepción NetworkOnMainThreadException.

 

Ejercicio: Un servidor de ECHO

Vamos a implementar un servidor de ECHO en tu ordenador personal. Has de tener claro que no va a ser una aplicación Android, si no un programa 100% Java. 

 

1.     Crea un nuevo proyecto Java y llámalo ServidorECHO.

Crea un nuevo proyecto (File > New Project..). Utiliza la opción Add No Activity, así no creamos una actividad que nunca será usada. Pulsa en File > New Module. Selecciona Java Library y pulsa Next. Introduce en Library name: servidor, como Java pakage name: com.example.servidorecho y en Java class name: ServidorECHO. Pulsa el botón Finish. Se creará un nuevo módulo Java dentro de tu proyecto Android. Pulsa en el botón desplegable a la derecha del botón Run . Selecciona Edit Configurations... En la nueva ventana, haz clic en el signo + de la esquina superior izquierda y selecciona Application. Aparecerá una nueva configuración de aplicación. Selecciona en Name: servidor, en Main class: com.example.servidorecho.ServidorECHO y en Use classpath of module: servidor. Pulsa en OK.

2.     Reemplaza el código de ServidorECHO con el siguiente código:

public class ServidorECHO {
   public static void main(String args[]) {
      try {
         System.out.println("Servidor en marcha...");
         ServerSocket sk = new ServerSocket(7);
         while (true) {
            Socket cliente = sk.accept();
            BufferedReader entrada = new BufferedReader(
                  new InputStreamReader(cliente.getInputStream()));
            PrintWriter salida = new PrintWriter(new OutputStreamWriter(
                  cliente.getOutputStream()), true);
            String datos = entrada.readLine();
            salida.println(datos);
            cliente.close();
            System.out.println(datos);
         }
      } catch (IOException e) {
         System.out.println(e);
      }
   }
}

En este caso utilizaremos la clase ServerSocket asociado al puerto 7 para crear un socket que acepta conexiones. Luego, se introduce un bucle infinito para que el servidor esté perpetuamente en servicio. El método accept() bloquea al servidor hasta que un cliente se conecte. Cuando ocurra esto, todo el intercambio de información se realizará a través de un nuevo socket creado con este propósito, el socket cliente. El resto del código es similar al cliente, aunque en este caso primero se recibe y luego se transmite lo mismo que ha recibido.

 

3.     Sustituye la dirección IP en ClienteECHO por la IP de tu ordenador. El comando ipconfig (Windows) o ifconfig (Linux/Mac) te permite averiguar la dirección IP de tu ordenador. No utilices como IP 127.0.0.1 (localhost) dado que, aunque se ejecuten en la misma máquina, la IP del emulador es diferente a la del PC.

4.     Ejecuta primero el servidor y luego el cliente para verificar que funciona.

 

NOTA: Si la IP de tu ordenador es privada, no podrás crear un servidor accesible desde cualquier parte de Internet. En este caso utiliza para el cliente un emulador o un dispositivo Android real que se conecte por Wi-Fia la misma red de tu ordenador. De lo contrario, el cliente no encontrará el servidor.

 

[1] http://developer.android.com/reference/android/os/StrictMode.html

[2]En el capítulo 5 se describe este problema con más detalle

 

- Un servidor por sockets para las puntuaciones

 

Siguiendo la estructura básica de un cliente y un servidor TCP que acabamos de ver, va a resultar muy sencillo implementar un protocolo que permita a varios clientes conectarse a un servidor para consultar la lista de puntuaciones o mandar nuevas puntuaciones.El primer lugar, tenemos que diseñar un protocolo que permita realizar las dos operaciones.

Para consultar puntuaciones el cliente se conectará al servidor y le mandará los caracteres PUNTUACIONES (solo se permite en mayúsculas) seguido de un salto de línea. El servidor mandará todo el listado de puntuaciones, separadas por caracteres de salto de línea. A continuación, se cerrará la conexión.

Cliente:                     PUNTUACIONES

Servidor:                   19000 Pedro Perez

17500 María Suarez

13000 Juan García

 Para almacenar una nueva puntuación el cliente se conectará al servidor y mandará un texto con la puntuación obtenida, seguido de un salto de línea. El servidor lo reconocerá como una nueva puntuación siempre que este texto no sea PUNTUACIONES. En tal caso almacenará la puntuación y mandará los caracteres OK seguidos de un salto de línea. A continuación se cerrará la conexión.

Cliente:        32000 Eva Gutierrez

Servidor:       OK

En segundo lugar, hay que elegir un número de puerto para realizar la comunicación; por ejemplo el 1234.

Ejercicio paso a paso: Estudio del protocolo PUNTUACIONES utilizando el comando telnet.

El siguiente ejercicio nos muestra un truco para testear si un servidor que utiliza TCP funciona correctamente. Te recomendamos que lo utilices antes de realizar la programación del cliente. Por una parte, te permitirá asegurarte que tanto la conexión, como el servidor funcionan correctamente. Por otra parte, te asegurarás que has entendido correctamente el protocolo a implementar.

NOTA: Para que el ejemplo funcione en la dirección IP 158.42.146.127 ha de haber un servidor de PUNTUACIONES en funcionamiento. En caso de no ser así, implementar tu propio servidor de PUNTUACIONES,  tal y como se muestra en uno de los siguientes ejercicios.

1.     Para conectarse al servidor desde un intérprete de comandos (símbolo del sistema/shell) escribe:

  telnet 158.42.146.127 1234

2.     Si se establece la conexión, escribe un número seguido de tu nombre y pulsa <Intro>. Por ejemplo:

 14000 Juan García

3.     La respuesta obtenida ha de ser OK y luego se cerrará la conexión.

4.     Repite el punto 2 y tras la conexión escribe:

  PUNTUACIONES

5.     La respuesta obtenida ha de ser la lista de puntuaciones donde ha de estar la que acabas de introducir.

Ejercicio paso a paso: Almacenando las puntuaciones mediante un protocolo basado en sockets

1.     Crea un nuevo proyecto Java  (Java SE, no para Android) y llámalo ServidorPuntuacionesSocket. Este proyecto ha de contener la clase ServidorPuntuaciones. Instrucciones detalladas se muestran en el ejercicio Un servidor de ECHO.

2.     Reemplaza en código por el que se muestra a continuación:

public class ServidorPuntuaciones {

    public static void main(String args[]) {
      Vector<String> puntuaciones = new Vector<String>();

      try {
         ServerSocket s = new ServerSocket(1234);
         System.out.println("Esperando conexiones...");

         while (true) {
            Socket cliente = s.accept();
            BufferedReader entrada = new BufferedReader(
                  new InputStreamReader(cliente.getInputStream()));
            PrintWriter salida = new PrintWriter(new OutputStreamWriter(
                  cliente.getOutputStream()), true);
            String datos = entrada.readLine();
            if (datos.equals("PUNTUACIONES")) {
               for (int n = 0; n < puntuaciones.size(); n++) {
                  salida.println(puntuaciones.get(n));
               }
            } else {
               puntuaciones.add(0, datos);
               salida.println("OK");
            }
            cliente.close();
         }
      } catch (IOException e) {
         System.out.println(e);
      }
   }
}

 

3.     Ejecuta el proyecto

4.     Verifica que en la vista Run aparece: "Esperando conexiones..."

5.     Desde la vista Run podrás detener la aplicación pulsando en el cuadro rojo.

NOTA:Si ejecutas de nuevo la aplicación sin pararla primero, dará un error. Esto es debido a que la aplicación ya lanzada no es detenida y esta aplicación tiene asociado el puerto 1234. El sistema no permitirá que una nueva aplicación escuche este puerto.

 

6.     Abre el proyecto Asteroides y crea la siguiente clase:

public class AlmacenPuntuacionesSocket implements AlmacenPuntuaciones{

  public AlmacenPuntuacionesSocket() {
    StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.
            Builder().permitNetwork().build());
  }

  public void guardarPuntuacion(int puntos, String nombre, long fecha){
   try {
      Socket sk = new Socket("X.X.X.X", 1234);
      BufferedReader entrada = new BufferedReader(
                new InputStreamReader(sk.getInputStream()));
      PrintWriter salida = new PrintWriter(
                new OutputStreamWriter(sk.getOutputStream()),true);
      salida.println(puntos + " " + nombre);
      String respuesta = entrada.readLine();
      if (!respuesta.equals("OK")) {
         Log.e("Asteroides", "Error: respuesta de servidor incorrecta");
      }
      sk.close();
   } catch (Exception e) {
      Log.e("Asteroides", e.toString(), e);
   }
  }

  public Vector<String> listaPuntuaciones(int cantidad) {
   Vector<String> result = new Vector<String>();
   try {
      Socket sk = new Socket("X.X.X.X", 1234);
      BufferedReader entrada =    new BufferedReader(
             new InputStreamReader(sk.getInputStream()));
      PrintWriter salida = new PrintWriter(
                new OutputStreamWriter(sk.getOutputStream()),true);
      salida.println("PUNTUACIONES");
      int n = 0;
      String respuesta;
      do {
         respuesta = entrada.readLine();
         if (respuesta != null) {
            result.add(respuesta);
            n++;
         }
      } while (n < cantidad && respuesta != null);
      sk.close();
   } catch (Exception e) {
      Log.e("Asteroides", e.toString(), e);
   }
   return result;
  }
}

7.     Sustituiye las dos apariciones de "X.X.X.X" por la dirección IP donde esté ejecutándose el servidor. 

NOTA: El comando ipconfig (Windows) o ifconfig (Linux/Mac)te permite averiguar la dirección IP de tu ordenador. No utilices como IP 127.0.0.1 (localhost) dado que, aunque se ejecuten en la misma máquina, la IP del emulador es diferente a la del PC.

8.     Recuerda que ahora la aplicación Asteroides necesita el permiso INTERNET. Y tiene que ser compilada con una versión mínima 9 (para StrictMode).

9.  Ejecuta la aplicación y accede a visualizar la lista de puntuaciones. Luego inicia una partida nueva.

10.  Verifica que en la vista Consola aparecen las consultas al servidor.

11.  Para terminar, reemplaza la IP por la siguiente "158.42.146.127" para conectarte a un servidor compartido. 

NOTA: Es posible que este servicio no haya sido iniciado.

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

 

13.  Comprueba si otros usuarios han accedido a este servidor y aparecen sus puntuaciones.

Preguntas de repaso: El interfaz socket

 

La web y el protocolo HTTP

Dentro del mundo de Internet destaca una aplicación que es, con mucho, la más utilizada: la World Wide Web (WWW), a la que nos referiremos coloquialmente como la web. Su gran éxito se debe a la facilidad de uso, dado que simplifica el acceso a todo tipo de información, y a que esta información es presentada de forma atractiva. Básicamente, la web nos ofrece un servicio de acceso a información distribuida en miles de servidores en todo Internet, que nos permite ir navegando por todo tipo de documentos multimedia gracias a un sencillo sistema de hipervínculos.

Para la comunicación entre los clientes y los servidores de esta aplicación, se emplea el protocolo HTTP (Hypertext Transfer Protocol), que será el objeto de estudio de este apartado.

- El protocolo HTTP

HTTP es un sencillo protocolo cliente-servidor que articula los intercambios de información entre los navegadores web y los servidores web. Fue propuesto por Tim Berners-Lee, atendiendo a las necesidades de un sistema global de distribución de información como la World Wide Web. En la web los servidores han de escuchar en el puerto 80, esperando la conexión de algún cliente web.

 

  video[Tutorial] El protocolo HTTP

A continuación describimos los pasos habituales que se siguen en una interacción del protocolo:

1)   El usuario quiere acceder a la página http://www.upv.es/dir/pag.html. Para lo cual pincha en un enlace de un documento HTML o introduciendo directamente en el campo Dirección del navegador Web.

2)   El navegador averigua la dirección IP de www.upv.es.

3)   El navegador establece una conexión con el puerto 80 de esta IP.

4)   El navegador envía por esta conexión (¿ carácter de salto de línea):

GET /dir/pag.html

5)   El servidor envía la página a través de la conexión:

    <HTML>

         <HEAD>

         <TITLE>Página de ... </TITLE>

         ...

         </HTML>

 

6)   El servidor cierra la conexión.

Este proceso se repite cada vez que el navegador necesita un fichero del servidor. Por ejemplo, si se ha bajado un documento HTML en cuyo interior están insertadas cuatro imágenes, el proceso anterior se repite un total de cinco veces, una para el documento HTML y cuatro para las imágenes.

Como ves, se trata de un protocolo sin estado. Cada petición contiene la información necesaria para ser atendida. Si deseamos mantener un estado tendrá que ser implementado usando algún mecanismo adicional (por ejemplo las cookies).

Ejercicio paso a paso: Estudio del protocolo HTTP/0.9 utilizando el comando Telnet.

1.     Abre un navegador web y accede a la página:

http://www.dcomg.upv.es/~jtomas/corta.html

En caso de que el servidor no responda, puedes realizar el ejercicio con cualquier página de otro servidor. El carácter ~ se obtiene pulsando simultáneamente <Alt Gr> y <4>.

2.     Visualiza el contenido HTML de la página (menú “Ver/Código fuente”, “Herramientas/ Ver código fuente”, o similar).

3.     Desde un intérprete de comandos (símbolo del sistema/shell) escribe:

telnet www.dcomg.upv.es 80

Este comando permite establecer una conexión TCP con el puerto 80 del servidor. A partir de ahora todo lo que escribas será enviado al servidor (aunque en mucho casos no lo veas en pantalla) y todo lo que el servidor envíe será impreso en pantalla..

NOTA: Si utilizas un servidor diferente, asegúrate de que soporta la versión HTTP/0.9.

 

4.     Cuando se establezca la conexión teclea exactamente:

GET /~jtomas/corta.html

Si te equivocas no uses la tecla de borrar. En tal caso, repite el ejercicio desde el punto 3.

5.   Observa que la respuesta obtenida coincide con el contenido HTML del paso 2.

 

- Versión 1.0 del protocolo HTTP

Con la popularización de la aplicación WWW, pronto se vio la necesidad de ampliar este sencillo protocolo para permitir nuevas funcionalidades. Se definió la versión 1.0 del protocolo que añadía nuevos métodos (PUT, POST,..) además de permitir el intercambio de cabeceras entre cliente y servidor.

  video[Tutorial] El protocolo HTTP v1.0

 

A continuación se muestra un ejemplo de interacción para la versión 1.0:

Cliente: GET /dir/pag.html HTTP/1.0

                  User-Agent: Internet Explorer v3.2

                  Host: www.upv.es

                  Accept: text/html, image/gif, image/jpeg

                                                 <línea en blanco>

Servidor:HTTP/1.1 200 OK

                  Server: Microsoft-IIS/5.0

         Last-Modified: Mon, 25 Feb 2002 15:49:22 GMT

         Content-Type: text/html

                                                 <línea en blanco>

                <HTML>

                  <HEAD>

                  <TITLE>Página de ... </TITLE>

                  ...

                  </HTML>

En esta nueva versión, el navegador se conecta al puerto 80 del servidor y normalmente le envía el comando “GET” seguido de la página que desea obtener. Ahora el navegador añadirá la palabra “HTTP/1.0” para indicar al servidor que quiere utilizar esta nueva versión del protocolo. A continuación, el navegador introducirá un salto de línea seguido, opcionalmente, de alguna cabecera (véase RFC 2616). Una cabecera consta de un identificador de cabecera, seguido de dos puntos y el valor de la cabecera, más un salto de línea (¿). Estas cabeceras permitirán que el navegador indique al servidor ciertos datos que pueden serle de utilidad. Por ejemplo, con el identificador de cabecera “User-Agent:” se puede indicar el tipo y la versión de navegador con el que se realiza la solicitud. “Host:” permite indicar el ordenador donde se ejecuta el servidor. O la cabecera “Accept:”, que permite indicar al servidor qué tipo de documentos es capaz de visualizar el navegador en formato MIME. Cuando el navegador ya no quiera insertar más cabeceras, introducirá una línea en blanco. Es decir, tras el salto de línea de la última cabecera, manda un nuevo salto de línea.

Cuando el servidor lea una línea en blanco, es decir, dos saltos de línea seguidos, sabrá que le ha llegado su turno y tiene que contestar. En esta versión del protocolo el servidor no transmitirá la página solicitada directamente. Antes contesta con una línea indicando: primero la versión más alta que soporta (normalmente “HTTP/1.1”), a continuación un espacio y un código de tres dígitos que informa de si se puede realizar la operación solicitada, finalizando con una frase explicativa sobre este código. Algunos códigos de respuesta posibles son: 200 OK, 401 no autorizado, 404 fichero no encontrado, etc. Tras esta primera línea el servidor podrá enviar alguna cabecera con información sobre el servidor o el documento que va a transmitir. Cuando ya no quiera insertar más cabeceras introducirá una línea en blanco seguida del documento.

Aunque el método GET es el más utilizado, en la versión 1.0 se añaden nuevos métodos. A continuación se incluye una descripción:

GET:                 Petición de lectura de un recurso.

POST:              Envío de información asociada a un recurso del servidor.

PUT:                 Creación de un nuevo recurso en el servidor.

DELETE:          Eliminación de un recurso.

HEAD:              El servidor solo transmitirá las cabeceras, no la página

Ejercicio paso a paso: Estudio del protocolo HTTP v1.0 utilizando el comando telnet.

1.     Desde un intérprete de comandos (símbolo del sistema/shell) escribe:

telnet www.dcomg.upv.es 80

2.     Cuando se establezca la conexión teclea:

 GET /~jtomas/corta.html HTTP/1.0

             

3.     Observa que la respuesta obtenida es similar al ejercicio anterior, pero ahora el servidor ha incluido cabeceras. ¿Qué información puedes sacar de estas cabeceras? ¿Por qué has tenido que introducir dos saltos de línea?

4.     Repite el ejercicio utilizando el comando HEAD.

Aunque la versión más reciente es HTTP/1.2 (y existe un borrador de HTTP/2.0), la versión más utilizada en la actualidad es HTTP/1.1. Incorpora algunas mejoras, como las conexiones persistentes, activadas por defecto o la gestión de la caché del cliente. También permite al cliente enviar múltiples peticiones a la vez.

 

- Utilizando HTTP desde Android

 

Tras el gran éxito de la web, el protocolo HTTP está siendo utilizado con finalidades diferentes de las que tenía en un principio: la descarga de páginas web. Por ejemplo, hoy en día es frecuente su uso para el intercambio de ficheros, la emisión de vídeo o la comunicación entre aplicaciones. A continuación describiremos las herramientas disponibles en Android para utilizar el protocolo HTTP. Para este propósito tenemos dos alternativas principales desde Android: el uso de las librerías java.net.* o org.apache.commons.httpclient.*. En el siguiente ejemplo utilizaremos las primeras.

En el ejemplo de este apartado vamos a extraer información de una de las páginas web más utilizadas en la actualidad: el servicio de búsqueda de Google. En concreto, nos interesa conocer el número de apariciones de una determinada secuencia de palabras en la web. En ciertas ocasiones esta información puede resultar muy interesante. Por ejemplo, tenemos dudas sobre el uso de una preposición en inglés: ¿se escribe travel in bus o travel by bus? Si buscamos ambas secuencias de palabras en Google, hay que ponerlas entre comillas para que busque la secuencia de forma literal. Obtenemos 240.000 apariciones para la primera y 1,1 millones para la segunda. Nuestra duda ha sido resuelta. Esta aplicación también puede utilizarse para averiguar quién es más popular en Internet, Antonio Banderas o Rafael Nadal.

 

Básicamente la aplicación que mostramos a continuación funciona de la siguiente forma:

1)   El usuario introduce una secuencia de palabras, por ejemplo “Antonio Banderas”.

2)   Accedemos al servidor mediante la siguiente URL:

http://www.google.es/search?hl=es&q=”Antonio+Banderas”

En este caso las peticiones se atienten mediante el método GET. En este método si queremos enviar información al servidor hemos de incluirla tras un carácter “?” seguido de un nombre de parámetro seguido del carácter “=” seguido del valor. Los diferentes parámetros se separan mediante el carácter “&”. Los espacios en blanco han de ser sustituidos por el carácter “+”. En este ejemplo el parámetro hl corresponde al idioma de búsqueda y q a las palabras a buscar.  

3)   Obtenemos la respuesta del servidor en una variable de tipo string.

4)   Buscamos la primera aparición de “Aproximadamente” en la respuesta.

5)   Tras esta palabra se encuentra la información que buscamos.

 Obviamente, el correcto funcionamiento de esta aplicación está sujeto a que no se produzcan cambios en la forma en que Google visualiza los resultados. En caso de que se realice algún cambio en esta página, va a ser necesaria una adaptación de nuestra aplicación. La técnica de sacar información directamente de una página web se conoce como web scraping. Hoy en día existe otra alternativa más fiable para obtener información. En el siguiente apartado mostraremos cómo utilizar un servicio web con este propósito.

Ejercicio paso a paso: Búsquedas en Google con HTTP 

1.     Crea un  nuevo proyecto con los siguientes datos:

Application name: HTTP

Package name: com.example.http

☑Phone and Tablet

Minimum SDK: API 9 Android 2.3 (Gingerbread)

Add an activity: Empty Activity

NOTA: Utilizamos la versión mínima la 2.3 para poder configurar StrictMode

2.     La aplicación ha de solicitar el permiso de acceso a Internet, añadiendo en AndroidManifest.xml la siguiente línea:

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

 

3.     Reemplaza el código del Layout activity_main.xml por:

<LinearLayout
   xmlns:android="http://schemas.android.com/apk/res/android"
   android:orientation="vertical"
   android:layout_width="match_parent"
   android:layout_height="match_parent">
   <EditText android:id="@+id/EditText01"
      android:layout_height="wrap_content"
      android:layout_width="match_parent"
      android:text="palabra a buscar"/>
   <Button android:id="@+id/Button01"
      android:layout_height="wrap_content"
      android:layout_width="wrap_content"
      android:onClick="buscar"
      android:text="buscar en Google"/>
   <TextView android:id="@+id/TextView01"
      android:layout_height="match_parent"
      android:layout_width="match_parent"
         android:textSize="8pt"/>
</LinearLayout>

 

La apariencia de este Layout se muestra a continuación:

 

4.     Reemplaza el código de MainActivity.java por:

public class MainActivity extends Activity {
   private EditText entrada;
   private TextView salida;

   @Override
   public void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      setContentView(R.layout.activity_main);
      entrada = (EditText) findViewById(R.id.EditText01);
      salida = (TextView) findViewById(R.id.TextView01);
      StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.
            Builder().permitNetwork().build());
   }

   public void buscar(View view){
      try {
         String palabras = entrada.getText().toString();
         String resultado = resultadosGoogle(palabras);
         salida.append(palabras + "--" + resultado + "\n");
      } catch (Exception e) {
         salida.append("Error al conectar\n");
         Log.e("HTTP", e.getMessage(), e);
      }      
   }
   
   String resultadosGoogle(String palabras) throws Exception {
      String pagina = "", devuelve = "";
      URL url = new URL("http://www.google.es/search?hl=es&q=\""
            + URLEncoder.encode(palabras, "UTF-8") + "\"");
      HttpURLConnection conexion = (HttpURLConnection)
            url.openConnection();
      conexion.setRequestProperty("User-Agent",
            "Mozilla/5.0 (Windows NT 6.1)");
      if (conexion.getResponseCode()==HttpURLConnection.HTTP_OK) {
         BufferedReader reader = new BufferedReader(new
            InputStreamReader(conexion.getInputStream()));
         String linea = reader.readLine();
         while (linea != null) {
            pagina += linea;
            linea = reader.readLine();
         }
         reader.close();
         devuelve = buscaAproximadamente(pagina);
      } else {
         devuelve = "ERROR: " + conexion.getResponseMessage();
      }
      conexion.disconnect();
      return devuelve;
   }

   String buscaAproximadamente(String pagina){
      int ini = pagina.indexOf("Aproximadamente");
      if (ini != -1) {
         int fin = pagina.indexOf(" ", ini + 16);
         return pagina.substring(ini + 16, fin);
      } else {
         return "no encontrado";
      }
   }

}

 

El comienzo del código ha de resultarte familiar hasta la última línea del método onCreate(). En esta línea se configura StrictMode[1]. para que permita  accesos a la red desde el hilo principal.

Pasemos a describir el método resultadosGoogle(). Este método toma como entrada una secuencia de palabras y devuelve el número de veces que Google las ha encontrado en alguna página web. Lo primero que llama la atención es el modificador throws Exception. Estamos obligados a incluirlo si utilizamos la clase HttpURLConnection. Este modificador obliga a utilizar el método dentro de una sección try … catch …. La razón de esto es que toda conexión HTTP es susceptible de no poder realizarse, por lo que tenemos la obligación de tomar las acciones pertinentes en caso de problemas.

Tras la declaración de variables, creamos la URL que utilizaremos para la conexión. El método URLEncoder.encode() se encargará de codificar las palabras en el formato esperado por el servidor. Entre otras cosas reemplaza espacios en blanco, caracteres no ASCII, etc. A continuación, preparamos la conexión por medio de la clase HttpURLConnection. Mediante el método setRequestProperty() podemos añadir cabeceras HTTP. En el punto 7 de este ejercicio se demuestra la necesidad de insertar la cabecera User-Agent.

En la siguiente línea se utiliza el método getResponseCode() para establecer la conexión. Si se establece sin problemas (HTTP_OK) leemos la respuesta línea a línea, concatenándola en el string pagina. El método buscaAproximadamente() busca en pagina la primera aparición de la palabra “Aproximadamente”. Si la encontramos, buscamos el primer espacio a continuación del número que ha de aparecer tras “Aproximadamente” y devolvemos los caracteres entre ambas posiciones. En caso de no encontrar “Aproximadamente”, puede que la secuencia buscada no se haya encontrado, aunque también es posible que Google haya cambiado la forma de devolver los resultados. En el caso de que la conexión no haya sido satisfactoria, devolvemos el mensaje de respuesta que nos dio el servidor getResponseMessage().

5.     Ejecuta la aplicación y verifica que funciona correctamente.

6.    En el código anterior comenta la línea:

conexion.setRequestProperty("User-Agent", ...);

7.     Ejecuta el programa. Ahora el resultado ha de ser:

ERROR: Forbidden

En este caso, al tratar de establecer la conexión el servidor, en lugar de devolvernos el código de respuesta 200: OK, nos ha devuelto el código 403: Forbidden. ¿Por qué? La cabecera User-Agent informa al servidor de qué tipo de cliente ha establecido la conexión. Según se demuestra en este ejercicio, el servicio de búsquedas de Google prohíbe la respuesta a aquellos clientes que no se identifican.

 

8.     Analiza el valor asignado a la cabecera “Mozilla/4.0 ...”. Puedes comprobar que la información que estamos dando al servidor es totalmente errónea.

9.     Modifica este valor por “Ejemplo de El gran libro de Android” y comprueba el resultado es 403: Forbidden ¿Por qué no quiere responder? La respuesta es que desde finales de 2011, el servidor de Google exige que el tipo de navegador que se conecte sea conocido.

 

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”.

 

- Comparativa sockets / servicios web

 

En este capítulo hemos utilizado dos alternativas, sockets y servicios web, para resolver un mismo problema. En la mayoría de los casos es más recomendable utilizar servicios web. Veamos las ventajas de un servicio web frente a un servidor de sockets:

La principal ventaja de los servicios web es la claridad de diseño. Para acceder al servicio resulta mucho más sencillo utilizar un método estándar muy conocido basado en URL, en lugar de tener que crear nuestro propio protocolo.

Otra ventaja es el aprovechamiento de las cabeceras HTTP. Como se comentó en el apartado anterior, el protocolo HTTP incorpora una serie de cabeceras para ofrecer información adicional en el intercambio. Mediante estas cabeceras podemos controlar aspectos muy importantes, como solicitar la autentificación del cliente, utilizar un modo seguro de transferencia (https), definir el tipo de información transmitida o controlar si queremos que las peticiones a nuestros servicios sean recordadas en la caché del cliente y por cuánto tiempo.

El uso de servidores comerciales en los servicios web nos proporciona grandes ventajas, que sería complejo implementar en nuestro servidor de sockets. Por ejemplo, en un servidor web como Apache se incluye la seguridad, la escalabilidad y facilidades de gestión.

Ambos servicios han de ofrecerse a través de un puerto. Los servicios web suelen utilizar el mismo puerto que los servidores web, el 80. Esto presenta la ventaja de tratarse de un puerto que raramente es filtrado por los cortafuegos. Esta ventaja también puede utilizarse en un servidor por socketssi le asignamos este puerto. Pero en este caso, ya no podrás instalar en la misma máquina un servidor web.


Preguntas de repaso:  Servicios web

La librería Volley

 

Volley[1] es un librería que permite realizar peticiones HTTP de forma sencilla sin tener que preocuparnos de la gestión de hilos. No pertenece al API de Android, pero ha sido desarrollada por Google, por lo que es posible que sea incluida en un futuro. Presenta las siguientes ventajas:

Gestión automática de hilos:No tendrás que crear nuevos hilos o AsyncTasck de forma manual. Solo tendrás que escribir el escuchador adecuado cuando se produzca la descarga.

Caché transparente:Las descargas son guardadas de forma automática en disco o memoria. Si se solicita un contenido ya descargado, la respuesta será inmediata. La caché es manejada gracias a las cabeceras del protocolo HTTP ( Last-Modified, If-Modified-Since, … ).

Manejo automático de colas de petición con prioridades:

Volley ha sido diseñada para realizar múltiples descargar simultáneas, pero no se recomienda su uso para la descarga de grandes volúmenes de datos. En este caso es más interesante usar la clase DownloadManager.



[1] https://developer.android.com/training/volley

- Descargar un String con Volley

Para usar esta librería primero has de solicitar el permiso de Internet y añadir en Gradle la siguiente dependencia:

compile 'com.android.volley:volley:1.0.0'

El siguiente paso es crear una cola de peticiones:

RequestQueue colaPeticiones = Volley.newRequestQueue(this);

Existen diferentes clases para crear peticiones. El siguiente nos devuelve el contenido en forma de String:

StringRequest peticion = new StringRequest(
      Request.Method.GET,
      "http://www.google.es/search?hl=es&q=busqueda",
      new Response.Listener<String>() {
         @Override
         public void onResponse(String respuesta) {
            …
         }
      },
      new Response.ErrorListener() {
         @Override
         public void onErrorResponse(VolleyError error) {
            …
         }
      }
);

Tiene cuatro parámetros: el método a usar (GET, POST, HEAD, …); la URL y dos escuchadores, uno para una respuesta satisfactoria y otro en caso de error.

Una vez creada la petición, la añadimos a la cola para que se ejecute:

colaPeticiones.add(peticion);

Existen cuatro clases según el tipos de datos a solicitar:

StringRequest(int method, String url, Response.Listener<String> listener, Response.ErrorListener errorListener)

ImageRequest(String url, Response.Listener<Bitmap> listener, int maxWidth, int maxHeight, Config decodeConfig, Response.ErrorListener errorListener)

JsonObjectRequest(int method, String url, JSONObject jsonRequest, Response.Listener<JSONObject> listener, Response.ErrorListener errorListener)

JsonArrayRequest(String url, Response.Listener<JSONArray> listener, Response.ErrorListener errorListener)

El parámetro method es optativo, de no indicarse se utiliza GET.Los constructores que no disponen de este parámetro utilizan por defecto GET. Las clases JSONObject y JSONArray pertenecen a la librería org.json incluida en el API de Android.

Ejercicio: Acceso HTTP con Volley 

1.    Abre el proyecto HTTP desarrollado en el ejercicio “Utilizando HTTP desde Android”, pero ahora utilizaremos la librería Volley en lugar de la clase HttpURLConnection.

2.   Añade al fichero Gradle Scripts/Bulid.gradle (Module:app) la dependencia:

dependencies {
    …
    compile 'com.android.volley:volley:1.0.0'
}

3.   Necesitamos crear una cola de peticiones. Añade en MainActivity la siguiente variable e iniciacizala en onCreate():

private RequestQueue colaPeticiones;

@Override
public void onCreate(Bundle savedInstanceState) {
    …
    colaPeticiones = Volley.newRequestQueue(this);
}

No resulta recomendable crear una nueva cola cada vez que necesitemos hacer una petición, por eso, vamos a crear una única cola para toda la actividad. Si vas a usar Volley en toda la aplicación puede ser interesante declarar la cola en la clase Application o en un singleton para trabajar con una cola única (descrito en El Gran Libro de Android Avanzado).

4.  En esta clase MainActivityañade el siguiente método:

 void resultadosGoogleVolley(final String palabras) throws Exception {

   StringRequest peticion = new StringRequest(
         Request.Method.GET,
         "http://www.google.es/search?hl=es&q=\""
                      + URLEncoder.encode(palabras, "UTF-8") + "\"",
         new Response.Listener<String>() {
            @Override
            public void onResponse(String respuesta) {
               String resultado = buscaAproximadamente(respuesta);
               salida.append(palabras + "--" + resultado + "\n");
            }
         },
         new Response.ErrorListener() {
            @Override
            public void onErrorResponse(VolleyError error) {
               salida.append("Error: " + error.getMessage());
            }
         }
   ) {
        @Override
        public Map<String, String> getHeaders() throws AuthFailureError {
           Map<String, String> cabeceras = new HashMap<String, String>();
           cabeceras.put("User-Agent", "Mozilla/5.0 (Windows NT 6.1)");
           return cabeceras;
     }
   };
   colaPeticiones.add(peticion);
}

A diferencia de lo realizado en ejercicios anteriores, este método no nos devuelve un Stringcon el resultado, por el contrario, va a modificar directamente la vista salida. Esto se debe a que Volley trabaja siempre de forma asíncrona. Los cuatro parámetros del constructor ya han sido explicados. Recuerda que el servidor de Google solo funcionaba si añadíamos una cabecera User-Agentválida. Para añadir esta cabecera, hemos de sobrescribir el método getHeaders()de la clase.

También puedes sobrescribir getMethod()para cambiar el método o getParams()para añadir parámetros usando el método POST.  

5.   Añade el siguiente método:

public void buscar4(View view){
    String palabras = entrada.getText().toString();
    try {
        resultadosGoogleVolley(palabras);
    } catch (Exception e) {
        e.printStackTrace();
    }
}

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

7.     Verifica el funcionamiento de la aplicación.

- Paso de parámetros con el método POST

Si quieres utilizar el método POST tentrás que indicarlo en el primer parámetro de la solicitud. Para las solicitudes ImageRequest y JsonArrayRequest no existe este parámetro y tendrás que configurarlo usando getMethod().

 Con el método POST los parámetros no se añaden a la URL, si no que son transmitidos tras las cabeceras. A continuación se muestra un ejemplo:

 

JsonArrayRequest peticion = new JsonArrayRequest(
         …
) {
  @Override
  public Map<String,String> getParams() {
     Map<String,String> parametros = new HashMap<String,String>();
     parametros.put("hl", "es");
     parametros.put("q", “hola");
     return parametros;
  }
  @Override
  public int getMethod() { return Method.POST; }
};

 

- Descargar imagenes con Volley

Disponemos de varias alternativas para descargar imágenes con Volley. La primera consiste en usar el método ImageRequest() que trabaja de forma similar al mostrado en el apartado anterior:

ImageRequest peticion = new ImageRequest(
      "http://mmoviles.upv.es/img/moviles.png",
      new Response.Listener<Bitmap>() {
         @Override
         public void onResponse(Bitmap bitmap) {
            miImageView.setImageBitmap(bitmap);
         }
      }, 0, 0, null, // maxWidth, maxHeight, decodeConfig
      new Response.ErrorListener() {
         @Override
         public void onErrorResponse(VolleyError error) {
            miImageView.setImage Resource(R.drawable.error_carga);
         }
      }
);
colaPeticiones.add(peticion);

Observa como este método tiene tres parámetros adicionales, donde podemos configurar como se va a decodificar la imagen. Se utilizan los valores por defecto, para más información consultar la documentación oficial.

Cuando queremos descargar múltiples imágenes de forma simultánea se recomienda usar la clase ImageLoader. La principal diferencia con el método anterior es que las imágenes se guardan en una caché en memoria, en lugar de en disco. Esto agiliza mucho el proceso y evita molestos parpadeos de las imágenes.

 El primer paso va a consistir en crear una instancia de ImageLoader:

RequestQueue colaPeticiones = Volley.newRequestQueue(this);
ImageLoader lectorImagenes = new ImageLoader(colaPeticiones,
   new ImageLoader.ImageCache() {
      private final LruCache<String, Bitmap> cache = new LruCache<String,
                                                             Bitmap>(10);
      public void putBitmap(String url, Bitmap bitmap) {
         cache.put(url, bitmap);
      }
      public Bitmap getBitmap(String url) {
        return cache.get(url);
      }
});

Como puedes ver un ImageLoader ha de estar asociado a un RequestQueue. Además ha de definir como se gestiona la caché, por medio de un objeto ImageCache.  En este objeto se define una estructura LruCache para almacenar en memoria pares de URL-Bitmaps. El valor 10, indica en máximo de elementos que queremos almacenar. Además, se definen dos métodos que permiten almacenar y recuperar elementos de la cahé.

Resulta interesante declarar un solo ImageLoader y RequestQueue en toda la aplicación. Como hemos comentado en el apartado anterior, un buen sitio para hacerlo es en la clase Application o en un Singleton.

Usar el ImageLoader es muy sencillo. No tienes más que llamar al método get() e indicarle la URL y un escuchador:

lectorImagenes.get("http://mmoviles.upv.es/img/moviles.png",
       ImageLoader.getImageListener(miImageView, R.drawable.por_defecto,
                                                 R.drawable.error_carga));

El escuchador será llamado cuando se descargue el BitMap y lo asignará al ImageView indicado. También se indican dos recursos que será asignados antes de la carga o en caso de error.

Disponemos de una tercera alternativa que consiste en usar la vista NetworkImageView, definida en Volley para trabajar conjuntamente con un ImageLoader. La nueva vista reemplazaría a ImageView, pero incorpora la posibilidad de cargar la imagen desde una URL. Trabajar con esta vista tiene la ventaja de que la descarga se puede sincronizar con la visualización: cuando la vista va a verse se puede iniciar la descarga y cuando deja de verse se puede cancelar la descarga.

Para usar esta alternativa reemplaza en un layout la etiqueta ImageView por la siguiente, dejando los atributos igual:

<com.android.volley.toolbox.NetworkImageView
    android:id="@+id/icono"
    android:layout_width=" match_parent"
    android:layout_height="match_parent"
    …"/>

Para asociar la URL utiliza el siguiente código:

icono = (NetworkImageView)itemView.findViewById(R.id.icono);
icono.setImageUrl("http://mmoviles.upv.es/img/moviles.png", lectorImagenes);

 

Ejercicio: Cargar imágenes de un RecyclerView con Volley

Cuando se trabaja con una lista generada con RecyclerViewes muy frecuente que cada elemento contenga una imagen que ha de descargarse de una URL. Se van a realizar múltiples peticiones simultáneas, por lo que, en este caso donde se recomienda el uso de ImageLoader.

1.    Abre el proyecto Asteroides y añade al fichero Gradle Scripts/Bulid.gradle (Module:app) la dependencia: compile 'com.android.volley:volley:1.0.0'.

2.    En la clase MainActivitydeclara las variables:

public static RequestQueue colaPeticiones;
public static ImageLoader lectorImagenes;

3.    En el método onCreate()inicializa estas variables como se acaba de ver. Recuerda que ya están declarados globalmente y has de quitar la clase antes del nombre del objeto.

4.    En la clase MiAdaptadordentro de onBindViewHolder()comenta el código:

switch (Math.round((float)Math.random()*3)){
    case 0:
        holder.icon.setImageResource(R.drawable.asteroide1);
        break;
    …
}

y reemplázalo por:

MainActivity.lectorImagenes.get("http://mmoviles.upv.es/img/moviles.png",
        ImageLoader.getImageListener(holder.icon, R.drawable.asteroide1,
                                                  R.drawable.asteroide3));

5.    Verifica el funcionamiento.

 

Ejercicio: Cargar imágenes de un RecyclerView con NetworkImageView

En el ejercicio anterior, hemos trabajando con vistas ImageView. En este ejercicio, vamos a reemplazarlas por NetworkImageView. De esta forma la gestión de la descarga puede sincronizarse con la visualización, obteniendo unos resultados óptimos.

1.   Edita el layout elemento_lista.xml reemplazando el <ImageView…  por <com.android.volley.toolbox.NetworkImageView….

2.   En la clase MiAdaptador, dentro de la clase  ViewHolder, reemplaza las dos apariciones de ImageView  por NetworkImageView.

3.   En la clase MiAdaptador, dentro de onBindViewHolder(), comenta el código introducido en el apartado anterior y reemplázalo por:

holder.icon.setImageUrl("http://mmoviles.upv.es/img/moviles.png",
                        MainActivity.lectorImagenes);

4.   Verifica el resultado.