jueves, 21 de febrero de 2013

Otro Cliente de Push Notification GCM

En esta ocasion, les quiero compartir la implementacion de un Cliente Android para las Notificaciones Push, tomado del sitio web Sgoliver .Net , un blog de Salvador Gómez. Dejo el texto con las imagenes tal cual está en el sitio web original.


Esta aplicación cliente, como ya hemos comentado en alguna ocasión será responsable de:
  1. Registrarse contra los servidores de GCM como cliente capaz de recibir mensajes.
  2. Almacenar el “Registration ID” recibido como resultado del registro anterior.
  3. Comunicar a la aplicación web el “Registration ID” de forma que ésta pueda enviarle mensajes.
  4. Recibir y procesar los mensajes desde el servidor de GCM.
Para las tareas 1 y 4 utilizaremos una librería específica de GCM que nos proporciona Google para facilitarnos la vida como desarrolladores (es posible hacerlo sin el uso de librerías externas, aunque requiere más trabajo). El punto 2 lo resolveremos fácilmente mediante el uso de SharedPreferences. Y por último el punto 3 lo implementaremos mediante la conexión al servicio web SOAP que creamos en el artículo anterior, sirviéndonos para ello de la librería ksoap2, tal como ya describimos en los artículos sobre servicios web SOAP en Android.
Durante el artículo construiremos una aplicación de ejemplo muy sencilla con el siguiente aspecto:
Aplicación Ejemplo Android GCM
Aplicación Ejemplo Android GCM
En esta aplicación, el usuario podrá introducir un nombre de usuario identificativo y pulsar el botón “Aceptar” para que quede guardado en las preferencias de la aplicación. Tras esto podrá registrarse como cliente capaz de recibir mensajes desde GCM pulsando el botón “Registrar GCM”. En caso de realizarse de forma correcta este registro la aplicación enviará automáticamente el Registration ID recibido y el nombre de usuario almacenado a la aplicación web a través del servicio web. Igualmente el usuario podrá des-registrarse en el servicio GCM para no recibir más mensajes pulsando el botón “Des-registrar GCM”. Obviamente todo este proceso de registro y des-registro debería hacerse de forma transparente para el usuario de una aplicación real, en esta ocasión he colocado botones para ello sólo por motivos didácticos.
Antes de nada vamos a preparar nuestro proyecto de Eclipse y vamos a configurar convenientemente el AndroidManifest para poder hacer uso del servicio GCM y su librería auxiliar.
Para ello vamos a crear un nuevo proyecto Android sobre un target de Android 2.2 o superior que incluya las librerías de Google, y vamos a incluir en su carpeta /libs las librerías de ksoap2 (esto ya vimos como hacerlo en el artículo sobre servicios web) y la librería cliente de GCM llamada “gcm.jar“. ¿Cómo podemos obtener esta librería? Para conseguirla debemos instalar desde el Android SDK Manager el paquete extra llamado “Google Cloud Messaging for Android Library”.
Descarga librería Google Cloud Messaging for Android
Librería Google Cloud Messaging for Android
Una vez instalado podemos ir a la ruta “RAIZ_SDK_ANDROID/extras/google/gcm/gcm-client/dist” donde deberá aparecer la librería “gcm.jar” que debemos añadir a nuestro proyecto.
Lo siguiente será configurar nuestro AndroidManifest. Lo primero que añadiremos será una cláusula <uses-sdk> para indicar como versión mínima del SDK soportada la 8 (Android 2.2). Con esto nos aseguraremos de que la aplicación no se instala en dispositivos con versión de Android anterior, no soportadas por el servicio GCM.
1
2
3
<uses-sdk
     android:minSdkVersion="8"
     android:targetSdkVersion="16" />
A continuación añadiremos los permisos necesarios para ejecutar la aplicación y utilizar GCM:
1
2
3
4
5
<permission android:name="net.sgoliver.android.permission.C2D_MESSAGE" android:protectionLevel="signature" />
<uses-permission android:name="net.sgoliver.android.permission.C2D_MESSAGE" />
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
Los dos primeros aseguran que sólo esta aplicación podrá recibir los mensajes, el segundo permite la recepción en sí de mensajes desde GCM (sustituir mi paquete java “net.sgoliver.android” por el vuestro propio en estas lineas), el tercero es el permiso para poder conectarnos a internet y el último es necesario para tareas realizadas durante la recepción de mensajes que veremos más adelante.
Por último, como componentes de la aplicación, además de la actividad principal ya añadida por defecto, deberemos declarar un broadcast receiver llamado GCMBroadcastReceiver, que no tendremos que crearlo porque ya viene implementado dentro de la librería “gcm.jar” (solo tenéis que modificar el elemento <category> para indicar vuestro paquete java), y un servicio llamadoGCMIntentService (Atención, es obligatorio este nombre exacto para el servicio si no queremos tener que implementar nosotros mismos el broadcast receiver anterior). Ya veremos más adelante para qué son estos dos componentes.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
    <application
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
    ...
    <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="net.sgoliver.android" />
        </intent-filter>
    </receiver>
    <service android:name=".GCMIntentService" />
</application>
Una vez definido nuestro AndroidManifest con todos los elementos necesarios vamos a empezar a implementar la funcionalidad de nuestra aplicación de ejemplo.
Empezamos por la más sencilla, el botón de guardar el nombre de usuario. Como comentamos anteriormente, vamos a utilizar preferencias compartidas para esta tarea. Como éste es un temaya visto en el curso no me detendré en el código ya que es bastante directo.
1
2
3
4
5
6
7
8
9
10
11
btnGuardarUsuario.setOnClickListener(new OnClickListener() {
    @Override
    public void onClick(View v) {
        SharedPreferences prefs =
             getSharedPreferences("MisPreferencias", Context.MODE_PRIVATE);
        SharedPreferences.Editor editor = prefs.edit();
        editor.putString("usuario", txtUsuario.getText().toString());
        editor.commit();
    }
});
Como podéis comprobar nos limitamos a almacenar una nueva propiedad llamada “usuario” con el texto introducido en el cuadro de texto de la interfaz.
El siguiente botón es el de registro del cliente en GCM, y aquí sí nos detendremos un poco para comentar primero cómo funciona internamente este procedimiento.
La aplicación android debe solicitar el registro en el servicio GCM mediante una petición HTTP POST similar a la que ya vimos en el artículo anterior para la aplicación web. Por suerte, este procedimiento se ve simplificado enormemente con el uso de la librería gcm.jar, ya que el montaje y ejecución de esta petición queda encapsulado como una simple llamada a un método estático de la clase GCMRegistrar, definida en la librería. Por su parte, tanto la respuesta a esta petición de registro como la posterior recepción de mensajes se reciben en la aplicación Android en forma de intents. Y aquí es donde entran en juego los dos componentes que hemos definido anteriormente en nuestro AndroidManifest. El receiver GCMBroadcastReceiver será el encargado de “esperar y capturar” estos intents cuando se reciban y posteriormente lanzar el servicio GCMIntentService donde se procesarán en un hilo independiente estas respuestas según el intent recibido. Como ya dijimos, el broadcast receiver no será necesario crearlo ya que utilizaremos el ya proporcionado por la librería. En cambio la implementación delIntentService sí será de nuestra responsabilidad. Aunque una vez más la librería de GCM nos facilitará esta tarea como ya veremos más adelante.
Veamos primero cómo realizar el registro de nuestra aplicación en GCM al pulsar el botón de registro. Como hemos dicho esto se limitará a llamar a un método estático, llamado register(), de la clase GCMRegistrar. La única precaución que tomaremos es verificar previamente si estamos ya registrados, algo que podremos hacer fácilmente llamando al métodogetRegistrationId() de la misma clase. El método register() recibirá dos parámetros, el primero de ellos una referencia al contexto de la aplicación (normalmente la actividad desde la que se llama) y en segundo lugar el “Sender ID” que obtuvimos cuando creamos el nuevo proyecto en la Google API Console.
1
2
3
4
5
6
7
8
9
10
11
12
13
btnRegistrar.setOnClickListener(new OnClickListener() {
    @Override
    public void onClick(View v) {
        //Si no estamos registrados --> Nos registramos en GCM
            final String regId = GCMRegistrar.getRegistrationId(GcmActivity.this);
            if (regId.equals("")) {
                GCMRegistrar.register(GcmActivity.this, "224338875065"); //Sender ID
            } else {
                Log.v("GCMTest", "Ya registrado");
            }
    }
});
Así de sencillo y rápido. Pero esto es sólo la petición de registro, ahora nos tocará esperar la respuesta, algo que veremos en breve.
Por su parte, el botón de “des-registro” se implementará de forma análoga, con la única diferencia que esta vez utilizaremos el método unregister() de la clase GCMRegistrar.
1
2
3
4
5
6
7
8
9
10
11
12
13
btnDesRegistrar.setOnClickListener(new OnClickListener() {
    @Override
    public void onClick(View v) {
        //Si estamos registrados --> Nos des-registramos en GCM
            final String regId = GCMRegistrar.getRegistrationId(GcmActivity.this);
            if (!regId.equals("")) {
                GCMRegistrar.unregister(GcmActivity.this);
            } else {
                Log.v("GCMTest", "Ya des-registrado");
            }
    }
});
Ahora toca procesar las respuestas. Como hemos dicho, para hacer esto tendremos que implementar el servicio GCMIntentService. Pero no lo haremos desde cero, ya que la librería de GCM nos proporciona una clase base GCMBaseIntentService de la cual podemos extender la nuestra, con la ventaja de que tan sólo tendremos que sobrescribir unos pocos métodos a modo de callbacks, uno por cada posible respuesta o mensaje que podemos recibir desde el servicio GCM. Estos métodos son:
  • onRegistered(context, regId). Se llamará al recibirse una respuesta correcta a la petición de registro e incluye como parámetro el Registration ID asignado a nuestro cliente.
  • onUnregistered(context, regId). Análogo al anterior pero aplicado a una petición de “des-registro”.
  • onError(context, errorId). Se llamará al recibirse una respuesta de error a una petición de registro o des-registro. El código de error concreto se recibe como parámetro.
  • onMessage(context, intent). Se llamará cada vez que se reciba un nuevo mensaje desde el servidor de GCM. El contenido del mensaje se recibe en forma de intent, el cual veremos más adelante cómo procesar.
Empecemos por el método onRegistered(). Al recibir una respuesta satisfactoria a la petición de registro recuperaremos el nombre de usuario almacenado y junto con el Registration IDrecibido nos conectaremos al servicio web que creamos en el artículo pasado pasándole dichos datos. Esto completará el registro tanto con el servidor de GCM como con nuestra aplicación web.
1
2
3
4
5
6
7
8
9
10
protected void onRegistered(Context context, String regId) {
    Log.d("GCMTest", "REGISTRATION: Registrado OK.");
    SharedPreferences prefs =
         context.getSharedPreferences("MisPreferencias", Context.MODE_PRIVATE);
    String usuario = prefs.getString("usuario", "por_defecto");
    registroServidor(usuario, regId);
}
El método registroServidor() será el encargado de realizar la conexión al servicio web y de la llamada al método web de registro. No me detendré en comentar el código de este método porque es análogo a los ejemplos ya vistos en el artículo que dedicamos a servicios web SOAP en Android. Veamos tan sólo el código:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
private void registroServidor(String usuario, String regId)
{
    final String NAMESPACE = "http://sgoliver.net/";
    final String METHOD_NAME = "RegistroCliente";
    final String SOAP_ACTION = "http://sgoliver.net/RegistroCliente";
    SoapObject request = new SoapObject(NAMESPACE, METHOD_NAME);
    request.addProperty("usuario", usuario);
    request.addProperty("regGCM", regId);
    SoapSerializationEnvelope envelope =
        new SoapSerializationEnvelope(SoapEnvelope.VER11);
    envelope.dotNet = true;
    envelope.setOutputSoapObject(request);
    HttpTransportSE transporte = new HttpTransportSE(URL);
    try
    {
        transporte.call(SOAP_ACTION, envelope);
        SoapPrimitive resultado_xml =(SoapPrimitive)envelope.getResponse();
        String res = resultado_xml.toString();
        if(res.equals("1"))
            Log.d("GCMTest", "Registro WS: OK.");
    }
    catch (Exception e)
    {
        Log.d("GCMTest", "Registro WS: NOK. " + e.getCause() + " || " + e.getMessage());
    }
}
Por su parte, en los métodos de des-registro y de error me limitaré a escribir un mensaje en el log de la aplicación para no complicar demasiado el ejemplo, pero en una aplicación real deberíamos verificar estas respuestas.
1
2
3
4
5
6
7
8
9
@Override
protected void onUnregistered(Context context, String regId) {
    Log.d("GCMTest", "REGISTRATION: Desregistrado OK.");
}
@Override
protected void onError(Context context, String errorId) {
    Log.d("GCMTest", "REGISTRATION: Error -> " + errorId);
}
Por último, en el método onMessage() procesaremos el intent con los datos recibidos en el mensaje y mostraremos una notificación en la barra de estado de Android. El intent recibido contendrá un elemento en su colección de extras por cada dato adicional que se haya incluido en la petición que hizo la aplicación servidor al enviar el mensaje. ¿Recordáis? Aquellos datos adicionales que había que preceder con el prefijo “data.”. Si hacéis memoria, en nuestros mensajes de ejemplo tan sólo incluíamos un dato llamado “data.msg” con un mensaje de prueba. Pues bien, estos datos se recuperarán de la colección de extras del intent llamado al método getString() con el nombre del dato, pero esta vez eliminando el prefijo “data.”. Veamos cómo quedaría todo esto:
1
2
3
4
5
6
@Override
protected void onMessage(Context context, Intent intent) {
    String msg = intent.getExtras().getString("msg");
    Log.d("GCMTest", "Mensaje: " + msg);
    mostrarNotificacion(context, msg);
}
Simple, ¿no?. Al final del método llamamos a un método auxiliar mostrarNotificacion() que será el encargado de mostrar la notificación en la barra de estado de Android. Esto también vimos como hacerlo en detalle en un artículo anterior por lo que tampoco comentaremos el código:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
private void mostrarNotificacion(Context context, String msg)
{
    //Obtenemos una referencia al servicio de notificaciones
    String ns = Context.NOTIFICATION_SERVICE;
    NotificationManager notManager =
        (NotificationManager) context.getSystemService(ns);
    //Configuramos la notificación
    int icono = android.R.drawable.stat_sys_warning;
    CharSequence textoEstado = "Alerta!";
    long hora = System.currentTimeMillis();
    Notification notif =
        new Notification(icono, textoEstado, hora);
    //Configuramos el Intent
    Context contexto = context.getApplicationContext();
    CharSequence titulo = "Nuevo Mensaje";
    CharSequence descripcion = msg;
    Intent notIntent = new Intent(contexto,
        GCMIntentService.class);
    PendingIntent contIntent = PendingIntent.getActivity(
        contexto, 0, notIntent, 0);
    notif.setLatestEventInfo(
        contexto, titulo, descripcion, contIntent);
    //AutoCancel: cuando se pulsa la notificaión ésta desaparece
    notif.flags |= Notification.FLAG_AUTO_CANCEL;
    //Enviar notificación
    notManager.notify(1, notif);
}
Y sólo una indicación más, además de sobrescribir estos métodos en nuestra clase GCMIntentService, también tendremos que añadir un nuevo constructor sin parámetros que llame directamente al constructor de la clase base pasándole de nuevo el Sender ID que obtuvimos al crear el nuevo proyecto en la Google API Console. Quedaría algo así:
1
2
3
public GCMIntentService() {
    super("224338875065");
}
Si no ha quedado claro del todo cómo quedaría la clase GCMIntentService completa puede descargarse y consultarse el código fuente completo al final del artículo.
Y con esto habríamos terminado de implementar nuestra aplicación Android capaz de recibir mensajes push desde nuestra aplicación web de ejemplo. Si ejecutamos ambas y todo ha ido bien, introducimos un nombre de usuario en la aplicación Android, pulsamos “Aceptar” para guardarlo, nos registramos como clientes en GCM pulsando el botón “Registrar GCM”, y seguidamente desde la aplicación web introducimos el mismo nombre de usuario del cliente, pulsamos el botón “Enviar GCM” y en breves segundos nos debería aparecer la notificación en la barra de estado de nuestro emulador como se observa en las imágenes siguientes:
Aplicación Ejemplo Android GCM
Aplicación Ejemplo Android GCM
Y si desplegamos la barra de estado veremos el mensaje de prueba recibido:
Notificación GCM Barra de Estado
Notificación GCM Barra de Estado
Como habéis podido comprobar en estos tres últimos artículos, la utilización de mensajes push requiere de un proceso algo laborioso pero para nada complicado. Os animo a que lo intentéis en vuestras aplicaciones ya que puede representar una característica interesante y útil.
Podéis descargar el código fuente del ejemplo construido en este artículo desde este enlace.
FUENTE: http://www.sgoliver.net/blog/?p=2879