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