Mecanismos para el envío de notificaciones a los usuarios de nuestra aplicación (parte II)
miércoles, agosto 8, 2012 at 8:23AM
rmontero
  1. Practicas con C2DM (Android Cloud to Device Messaging) ahora GCM (Google Cloud Messaging for Android)

 Mientras escribía esta segunda parte del tutorial de envío de notificaciones, el API de C2DM ha sido declarado obsoleto, siendo sustituido por un nuevo API y sus servicios asociados, denominado GCM – Google Cloud Messaging for Android. Esto no afecta a las aplicaciones/ usuarios o desarrolladores que estén utilizando el “antiguo” C2DM, declarado obsoleto a finales del mes de Junio de 2012, ya que seguirá proporcionando servicios, con la salvedad de que la plataforma ya no acepta nuevas cuentas de desarrollador. Por lo tanto, en este tutorial nos centraremos en estudiar de forma práctica el nuevo servicio GCM.

    

Al igual que con el API C2DM, en el envío de una notificación PUSH generalmente intervienen los siguientes actores: dispositivo móvil, servidores GCM de Google, servidor de aplicaciones propio y un navegador Web.

Conociendo los actores que intervienen y en el orden que lo hacen (secuencia de acciones detallada en la primera entrega), extraemos dos tipos de desarrollo de software a realizar en esta practica: uno sobre el API de Android correspondiente a la aplicación instalada en el dispositivo móvil y otro basado en J2EE que se desplegará sobre un servidor de aplicaciones (Tomcat) con el fin de administrar y enviar las notificaciones a los dispositivos móviles subscritos.

 2. Requisitos previos a la práctica

El software que deberá tener instalado el desarrollador para llevar a cabo esta práctica es:

 Además de los requisitos de software, en cuya instalación no nos detendremos, antes de comenzar a desarrollar también debemos de: 

  2.1. Registrar una cuenta de desarrollador y un proyecto en los servicios GCM

A continuación detallaremos los sencillos pasos a realizar para crear una cuenta de desarrollador y dar de alta una aplicación como consumidora del API de GCM.

Primero accederemos a la consola “Google APIs Console” con nuestra cuenta de Google. Si no tenemos todavía ninguna aplicación registrada en este servicio, comenzaremos pulsando el botón “Create Project”:

Al pulsar sobre el botón “Create Project” automáticamente el navegador nos redirigirá a una URL similar a: https://code.google.com/apis/console/?pli=1#project:6757673023114:services donde “6757673023114” será el identificador asignado por Google a nuestro proyecto. Este ID lo usaremos posteriormente para gestionar las notificaciones.

La URL anterior mostrará todos los servicios de Google de los que podremos hacer uso en nuestras aplicaciones. En este caso nos centraremos en el denominado “Google Cloud Messaging for Android”, que será necesario activar:

Google nos proporciona diferentes formas de identificación remota antes sus servicios, tal y como podremos ver si pulsamos sobre la opción “API Access” del menú situado en el lateral izquierdo del navegador:

En este tutorial obviaremos la identificación OAuth, para usar el “Simple API Access”, por ello apuntaremos el “API key” que aparece en la parte inferior de la pantalla.

 2.2  Descargar librerías

Antes de comenzar el desarrollo de nuestras aplicaciones basadas en los servicios GCM debemos descargar las librerías que nos facilitan el acceso a los mismos.

De momento nos limitaremos a la descarga de dichas librerías, posteriormente, a lo largo de este tutorial, descubriremos la finalidad de cada una de ellas.

Para acceder al software ejecutamos el SDK Manager, marcando la opción extras/Google Cloud Messaging for Android Library:

Al pulsar botón “Install” comenzarán a descargarse las librerías relacionadas con GCM. Cuando finalice la descarga las encontraremos en la ruta $SDK_HOME/extras/Google/GCM:

 

 

A continuación crearemos desde Eclipse una aplicación Android basada en la versión 4.1. Su objetivo será recibir las notificaciones PUSH de los servidores GCM y mostrar el texto en un mensaje “log”. Lo habitual es mostrar los mensajes recibidos en el área de notificaciones del usuario, pero en este tutorial, con objeto de simplificar, solamente los mostraremos en mensajes de la consola “debug”. En el momento de la instalación en el dispositivo cliente, la aplicación se registrará ante los servidores de Google como consumidora de notificaciones. Google le asignará un identificador único que la aplicación deberá de enviar a nuestro servidor Tomcat. Esta clave será utilizada posteriormente para identificar a que dispositivo notificar.

Los pasos para construir una aplicación consumidora de notificaciones son:

  1. Copiar las librerías del API GCM a nuestro proyecto. 
  2. Configuración de permisos en el descriptor AndroidManifest.xml 
  3. Registro de BroadcastReceivers e implementación de servicios consumidores. 
  4. Implementación de la Actividad principal 

3.1 Copiar las librerías del API GCM a nuestro proyecto.

Será en este momento en el que comenzaremos a utilizar el API descargado mediante la SDK Manager el apartado 2.2. Copiaremos la librería gcm.jar ubicada en gcm-client/dist a la carpeta libs de nuestro proyecto Eclipse:

3.2. Configuración de permisos en el descriptor AndroidManifest.xml.

Para recibir notificaciones mediante el servicio GCM necesitaremos solicitar una serie de permisos que como viene siendo habitual, registraremos en el archivo AndroidManifest.xml a través de la etiqueta uses-permission.

3.2.1. Conexión a internet

 El permiso más obvio será el que nos concederá acceso a la red de datos:

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

3.2.2       Acceso a información de las cuentas de usuario vinculadas al dispositivo

El API GCM requiere de una cuenta Google vinculada, por ello añadiremos el siguiente permiso:

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

3.2.3       Evitar que la CPU se “duarma” cuando se recibe un mensaje

Para mejorar la duración de la batería, el S.O puede poner la CPU en estado de "sleep" o atenuar el brillo de la pantalla cuando no se ha producido ninguna interacción del usuario con el terminal. Este hecho en algunos casos puede poner en apuros la recepción de nuestros mensajes PUSH, por ello trataremos de evitarlo usando el siguiente permiso:

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

3.2.4       Permisos específicos para recibir notificaciones PUSH del servicio GCM

Será necesario un permiso específico para recibir notificaciones PUSH del servicio GCM. Tal y como se puede observar su denominación es una herencia de C2DM:

<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />

Si el dispositivo sobre el que estamos ejecutando la aplicación dispone de una versión inferior 4.1 (API 16) también será necesario incluir los siguientes permisos:

<permission android:name="my_app_package.permission.C2D_MESSAGE" android:protectionLevel="signature" />

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

Por ejemplo, en nuestro caso tendríamos:

<permission android:name="org.javahispano.testnotificacion.permission.C2D_MESSAGE" android:protectionLevel="signature" />

<uses-permission android:name="org.javahispano.testnotificacion.permission.C2D_MESSAGE" />

3.3  Registro de BroadcastReceivers e implementación de servicios consumidores.

El siguiente paso será registrar el BroadcastReceiver encargado de recibir el identificador de registro en el GCM y las notificaciones emitidas por el servidor. Al contrario que en el antiguo C2DM, este escuchador es implementado por la propia librería y tan sólo tendremos que registrarlo en el AndroidManifest.xml, tal y cómo se muestra a continuación: 

 

<receiver android:name="com.google.android.gcm.GCMBroadcastReceiver" android:permission="com.google.android.c2dm.permission.SEND" >

<intent-filter>

<action android:name="com.google.android.c2dm.intent.RECEIVE" />

<action android:name="com.google.android.c2dm.intent.REGISTRATION" />

<category android:name="my_app_package" />

</intent-filter>

</receiver>

 

 La labor del desarrollador se limitará a implementar un servicio que extienda de la superclase: com.google.android.gcm.GCMBaseIntentService. De esta clase debemos escribir los siguientes métodos: 

 Los métodos enumerados serán invocados por el componente GCMBroadcastReceiver registrado anteriormente.

 

A continuación implementamos el servicio con los métodos anteriores. De momento sólo escribiremos un traza de tipo debug en cada uno de ellos, para demostrar su ciclo de vida: 

 

package org.javahispano.testnotificacion;

 

 

import android.content.Context;

import android.content.Intent;

import android.util.Log;

 

import com.google.android.gcm.GCMBaseIntentService;

 

public class GCMIntentService extends GCMBaseIntentService{

 

private static final String SENDER_ID ="6757673023114";

public GCMIntentService() {

super(SENDER_ID);

}

 

@Override

protected void onError(Context ctx, String registrationId) {

Log.d("javahispano",

"Error recibido");

 

}

 

@Override

protected void onMessage(Context ctx, Intent intent) {

String msg = intent.getExtras().getString("msg");

Log.d("javahispano",

"Mensaje:" + msg);

 

}

 

@Override

protected void onRegistered(Context ctx, String regId) {

//Posteriormente enviaremos el id al Tomcat

Log.d("javahispano", "Registro recibido " + regId);

}

 

@Override

protected void onUnregistered(Context ctx, String regId) {

//Posteriormente enviaremos el id al Tomcat

Log.d("javahispano",

"Baja:" + regId);

}

 

}

Del listado anterior cabe destacar que hemos definido un constructor público para invocar al constructor de la clase “padre” junto con el “Project Id” que vimos en el apartado 2.1.

Finalmente no debemos olvidar registrar este servicio en el AndroidManifest.xml:

 

<service android:name=".GCMIntentService" />

 

3.4  Implementación de la actividad principal

La actividad principal de la aplicación será la que desencadenará el arranque de los componentes declarados anteriormente (GCMBroadcastReceiver y GCMIntentService). Para ello utilizaremos la clase com.google.android.gcm.GCMRegistrar, de la cual destacaremos 4 métodos:

A continuación usaremos los cuatro métodos enumerados en el onCreate de nuestra actividad:

 

import com.google.android.gcm.GCMRegistrar;

 

public class MainActivity extends Activity {

 

 private static final String SENDER_ID ="6757673023114";

 

@Override

public void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

 

GCMRegistrar.checkDevice(this);

GCMRegistrar.checkManifest(this);

 final String regId = GCMRegistrar.getRegistrationId(this);

 

 if (regId.equals("")) {

//Lanzamos el registro

GCMRegistrar.register(this, SENDER_ID);

} else {

Log.v("javahispano", "Ya esta registrado");

}

}

 

@Override

public boolean onCreateOptionsMenu(Menu menu) {

getMenuInflater().inflate(R.menu.activity_main, menu);

return true;

}

 

 

}

 

Llegados a este punto la aplicación ya estará lista para ser probada en un emulador basado en el Google APIs. Al ejecutar la aplicación podremos encontrar los logs escritos por nuestro programa en la ventana LogCat de Eclipse:

 

  4. Aplicación Java EE para gestionar los dispositivos subscritos y el envió de notificaciones

 

En este apartado construiremos una aplicación J2EE con dos funcionalidades principales:

 

Comenzaremos creando un nuevo proyecto Web mediante el Wizard de Eclipse (File >New > Project):

 

 

A continuación daremos un nombre al proyecto:

 

 En el campo “Target runtime” seleccionaremos la instalación del servidor Tomcat 6 que tengamos instalado en el equipo. Esto lo realizaremos mediante el botón “New Rutime”:

Para terminar la configuración del servidor pulsaremos el botón “Finish”. Se habrá creado un nuevo proyecto con la siguiente estructura:

    Una vez que tenemos el esqueleto de la aplicación Web, seguiremos los siguientes pasos:

  1. Copiar las librerías del API GCM Server a nuestro proyecto.

  2. Implementar un servlet que reciba y almacene los ID de los dispositivos.

  3. Implementar un servlet para gestionar las notificaciones a los dispositivos.

  4.1. Copiar las librerías del API GCM Server a nuestro proyecto

 

Al igual que en el caso de la aplicación Android, necesitaremos copiar las librerías de la parte servidora en nuestro proyecto Web. Para ello tomamos la librería gcm-server.jar descargada previamente mediante el SDK Manager y la copiamos al directorio WEB-INF/lib de nuestro proyecto:

La librería gcm-server.jar tiene dependencias que también debemos añadir al proyecto. En nuestro caso será con la librería json_simple-1.1.jar que encontraremos en la carpeta lib.

4.2. Implementar un servlet que reciba y almacene los ID de los dispositivos

 Crearemos un servlet que escuche sobre la URL RegistrationServlet y por el cual recibamos los identificadores GCM asignados a los dispositivos que se han instalado nuestra aplicación.

Para crear un nuevo servlet con Eclipse seleccionaremos la opción File > New > Servlet:

 

  Al pulsar sobre el botón Finish se abrirá nuestro primer servlet. La programación de este componente será muy sencilla, simplemente recogerá de la request el parámetro “ID” para almacenarlo en una variable estática de la clase JHHelper:

 

public class RegistrationServlet extends HttpServlet {

 

public RegistrationServlet() {

super();

}

 

 protected void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {

String newDevice = req.getParameter("ID");

JHHelper.devices.add(newDevice);

}

 

 protected void doPost(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {

 doGet( req, res);

}

 

}

 

Donde la clase JHHelper esta tan sencilla como exponemos a continuación:

 

public class JHHelper {

public static HashSet<String> devices = new HashSet<String>();

}

 

  4.2.1. Modificar la aplicación Android para enviar el ID de registro al nuevo servlet

 

Una vez que hemos construido el servlet, podremos modificar la aplicación Android para que le envíe el ID de registro.

No debemos olvidar que para establecer conexiones HTTP, Android incorpora nativamente las librerías Apache HttpClient. Basada en ellas hemos construido una clase llamada JHHelper encargada de establecer la conexión con este nuevo servlet enviándole el ID de registro recibido:

 

import java.io.IOException;

import java.util.Arrays;

 

import org.apache.http.NameValuePair;

import org.apache.http.client.ClientProtocolException;

import org.apache.http.client.HttpClient;

import org.apache.http.client.entity.UrlEncodedFormEntity;

import org.apache.http.client.methods.HttpPost;

import org.apache.http.impl.client.DefaultHttpClient;

import org.apache.http.message.BasicNameValuePair;

 

import android.content.Context;

import android.os.AsyncTask;

 

import android.util.Log;

 

public class JHHelper {

 

static final String URL_SERVLET = "http://192.168.0.192:8080/JavaHispanoWeb/RegistrationServlet";

 

public static void registerTomcat(final Context context, String registration) {

Log.d("javahispano", "Registrando en tomcat");

new AsyncTask<String, Void, String>() {

 

@Override

protected String doInBackground(String... registrations) {

return register(context, registrations[0]);

}

 

@Override

protected void onPostExecute(String registration) {

}

 

private String register(Context context, String registration) {

 

HttpClient client = new DefaultHttpClient();

 

try {

HttpPost post = new HttpPost(URL_SERVLET);

 

post.setEntity(new UrlEncodedFormEntity(

Arrays.asList(new NameValuePair[] { new BasicNameValuePair("ID",

registration) })));

int httpStatus = client.execute(post).getStatusLine().getStatusCode();

 

if (httpStatus < 400) {

Log.d("javahispano", " El https status " + httpStatus);

return registration;

} else {

Log.d("javahispano", " El https status es mas de 400 " + httpStatus);

}

 

} catch (ClientProtocolException exception) {

Log.e("javahispano", " Error al conectar con el servidor TOMCAT");

exception.printStackTrace();

} catch (IOException exception) {

Log.e("javahispano", "Error 2 al conectar con el servidor TOMCAT");

exception.printStackTrace();

}

 

return null;

 

}

 

}.execute(registration);

}

 

}

 

La clase anterior expone el método estático registerTomcat que mediante un Thread asíncrono se conectará al servidor para enviarle su id de registro como aplicación consumidora de las notificaciones.

A continuación modificaremos la clase GCMIntentService expuesta previamente para incluir la llamada al listado anterior en su método onRegistered:

 

@Override

protected void onRegistered(Context ctx, String regId) {

Log.d("javahispano", "Registro recibido " + regId);

JHHelper.registerTomcat(ctx, regId);

}

 

 

 4.3. Implementar un servlet para gestionar las notificaciones a los dispositivos

 

A continuación crearemos un nuevo servlet llamado NotificationServlet, donde programaremos los métodos:

Comenzaremos viendo el método doGet:

 

protected void doGet(HttpServletRequest request, HttpServletResponse response)

throws ServletException, IOException {

Iterator iterator;

iterator = JHHelper.devices.iterator();

 

response.setContentType("text/html");

PrintWriter out = response.getWriter();

 

out.println("<html><title>.::JavaHispano::.</title>"

+ "<body bgcolor=FFFFFF>");

out

.println("<form name='notificacion' action='NotificationServlet' method='post'>");

out.println("<h2>Envio de notificaciones</h2>");

out.println("<h6>Seleccione dispositivos a notificar</h6>");

out.println("<SELECT NAME='DEVICES' MULTIPLE SIZE=5>");

while (iterator.hasNext()) {

String id = (String) iterator.next();

out.println("<OPTION VALUE='" + id + "'>" + id + "</OPTION>");

}

out.println("</SELECT>");

out.println("<h6>Escriba el texto a enviar</h6>");

out.println("<TEXTAREA COLS=20 ROWS=10 NAME='MSG'></TEXTAREA> ");

out.println("<input type='submit' value='Enviar'/>");

out.println("</form></body></html");

out.close();

}

 

  A continuación veremos el método doPost: 

import com.google.android.gcm.server.Message;

import com.google.android.gcm.server.MulticastResult;

import com.google.android.gcm.server.Sender;

private static final String API_KEY = "AIzaSfasddsfodfassdfO_l0jy_5wcTdasfaiOfsDASFClE0";

protected void doPost(HttpServletRequest request, HttpServletResponse response)

throws ServletException, IOException {

response.setContentType("text/html");

PrintWriter out = response.getWriter();

out.println("<html><title>.::JavaHispano::.</title><body>");

;

 

String[] devices = request.getParameterValues("DEVICES");

 

Sender sender = new Sender(API_KEY);

Message.Builder msgBuilder = new Message.Builder();

msgBuilder.addData("msg", request.getParameter("MSG"));

 

MulticastResult result = sender.send(msgBuilder.build(),

Arrays.asList(devices), 5);

out.println("<h3>NUMERO DE MENSAJES NO ENVIADOS : " + result.getFailure()

+ "</h3>");

out.println("<h3>NUMERO DE MENSAJES ENVIADOS : " + result.getSuccess()

+ "</h3>");

 

out.println("</body></html>");

out.close();

}

 

 

En el listado anterior indicaremos al servicio GCM los dispositivos a notificar a través de la clase com.google.android.gcm.server.Sender, que usará el API KEY del proyecto, tal y como vimos en los primeros apartados de este tutorial. Para construir el mensaje a enviar usaremos la clase com.google.android.gcm.server.Message.

Llegados a este punto nuestra aplicación estará lista para ser usada.

 

  5. Probando la aplicación

 Para poder probar la aplicación lo primero que haremos será desplegar la parte servidora en el Tomcat. Para ello en la pestaña “Servers” de Eclipse pulsamos añadir nuevo servidor:

 Una vez seleccionado el servidor Tomcat 6, en la siguiente pantalla nos solicitará las aplicaciones que queremos desplegar en él. Instalaremos nuestra aplicación JavaHispanoWeb:

  Arrancaremos el servidor tal y como se muestra en la siguiente figura:

 Una vez arrancado el servidor, podremos instalar las aplicaciones en los terminales móviles (en nuestro caso en el emulador). La aplicación cliente ya podrá enviar el ID de registro al servidor Tomcat.

Si escribimos la URL http://localhost:8080/JavaHispanoWeb/NotificationServlet accederemos a la página de notificaciones de la aplicación JavaHispanoWeb:

 En la lista superior aparecerán los dispositivos subscritos a nuestro servicio. Cuando pulsemos enviar, el texto indicado será notificado a cada uno de los dispositivos seleccionados.

Cuando el terminal móvil (en nuestro caso el emulador) reciba la notificación, podremos verla a través de los log en la pestaña de LogCat:

 

A lo largo de este tutorial hemos estudiado el funcionamiento de las notificaciones PUSH GCM tanto desde el punto de vista del cliente, como del servidor. Con el fin de simplificar el código para su fácil compresión, hemos obviado algunos aspectos como por ejemplo, dar de baja los dispositivos que no deseen seguir subscritos en la aplicación o la gestión de errores. Aunque estos aspectos no son importantes para entender el funcionamiento del servicio GCM, sí que lo serán si deseamos hacer pública nuestra aplicación.

Article originally appeared on javaHispano (http://www.javahispano.org/).
See website for complete article licensing information.