lunes, 18 de febrero de 2013

GCM, Notificaciones Push en Android (COMPLETO)


Sin duda, si sos un programador medianamente avanzado,  te interesan las “Push Notifications” y/o has programado o tienes previsto programar Apps que hagan uso de esta forma de comunicación. Hablaremos de Notificaciones Push en Android, sumate, dale?


En esta ocasión presentamos la implementación de las “Push Notification” que han hecho los developers de Google y la adaptación a un entorno relativamente simple. Bastantes ideas hubo en los círculos de desarrolladores entorno a Google I/O 2012 que tuvo lugar los días 27 y 28 de Junio de 2012. En este evento fue donde presentaron la maduración de un proyecto bastante interesante llamado Google Cloud Messaging, maduración del ya antiguo C2DM.
Decir que con el sistema GCM podremos llevar a nuestras plataformas móviles los desarrollos propios que implementen o hagan uso de las llamadas “Push Notification” o “Mensajes Push”, es lo que algunos ya habran escuchado. Ahora está en boca de todos hablar sobre estos temas pero, exactamente ¿qué quiere decir notificaciones push?. Bueno no sabemos si exactamente o no, pero lo que debemos de tener muy claro es que es una forma de programar que se ideó justo cuando salieron al mercado los actuales gestores de correo. Es la forma en la que las Apps clientes son informadas sobre ciertas novedades o eventos evitando tener que consultar el estado del buzón de entrada de forma automática. Bueno ¿y para qué? En primer lugar ahorramos en la tarifa del pack de datos, ya que las consultas al servidor se harán de forma pasiva, solo se realizarán cuando el servidor tenga algo nuevo que decir; en segundo lugar, y como es lógico pensar, ahorraremos en el consumo de batería, ya que se hace un uso optimizado de los recursos del teléfono.
WhatsApp, Google Talk, Facebook Messenger? Si usas alguno de esos, seguro que sabes que tienen algo en común. Y si, el sistema de comunicaciones se hace a través de Notificaciones Push. Vamos a repasar este tema y ver como funcionan, desarrollando Apps en Android.


Ya hace bastante tiempo que estas aplicaciones se han instalado en nuestras vidas y que han conseguido desbancar casi por completo a los obsoletos SMS. Ahora gracias al nuevo servicio de Google Cloud Messaging, podemos crear nuestro propio sistema de notificaciones Push en nuestras aplicaciones. Pensá como puede llegar a mejorar nuestra aplicación con un sencillo servicio de notificaciones, la experiencia del usuario cambia bsatante, en aplicaciones puntualmente enfocadas Conferencias, Agendas de Eventos que utilizando la API de Calendar (si, utilizando y configuracion el calendario del usuario en su Android).

Voy a tratar de explicar brevemente como sería el proceso de envío. En el primer paso nuestro dispositivo se registra de forma totalmente transparente al usuario en el sevidor de Google para decirle que puede recibir notificaciones para nuestra aplicación. En este registro el servidor de Google nos devuelve un Identificador a nuestra aplicación Android que deberemos almacenar en un servidor de nuestra propiedad. Una vez ya están todos presentados, desde nuestro servidor ya se pueden empezar a enviar mensajes. Para esto se envían pedidos al servidor de Google y este se encarga de enviarlo si el dispositivo está online o de esperar a que el dispositivo se reconecte para enviarlo.
Toda la informacion que aqui vamos a compartir es una recopilacion de distintas paginas webs de referencia.

Requisitos previos a la práctica

El software que deberá tener instalado el desarrollador para llevar a cabo esta práctica es:
  •  JDK (1.6 )
  • Tomcat (6)
  • Plataforma de desarrollo Android (SDK, Eclipse for Java EE Developers y plugin ADT para eclipse )
 Además de los requisitos de software, en cuya instalación no nos detendremos, antes de comenzar a desarrollar también debemos de: 
  • Disponer de una cuenta gratuita en los servicios de Google APIs y registrar el nuevo proyecto.
  • Disponer de las librerías necesarias para manipular el API GCM.
Lo primero que debemos hacer es activar el servicio de Google Cloud Messaging en la siguiente dirección https://code.google.com/apis/console. Tenes que disponer de una cuenta de Google para poder realizar este proceso.




Creamos un proyecto el cual nos dará un senderId que deberemos utilizar en la app Android. Lo pdemos encontrar en la pestaña Overview, sección Project Number.

Lo siguiente es activar el servicio de mensajería. Para esto en la pestaña Services buscá Google Cloud Messaging for Android y lo pones a ON. Ya por último, vas a API Access y pulsas el botón "Create new Server key" (en la pantalla que aparece no hace falta rellenar nada). Esto nos creará una apiKey, ojo, la de la sección Key for server apps. La cual utilizaremos después.

Esta es toda la configuración que necesitamos en el servidor de Google.

Vamos ahora a crear la parte servidor. Para ello haremos como es habitual en PHP y MySQL, pero lo podes adaptar de una forma muy simple al lenguaje que necesites.

Lo primero que vamos a hacer es crear una tabla en nuestra base de datos para almacenar los usuarios que se registren en nuestra app para saber los registration id que nos proporcionó Google Cloud Messaging.


CREATE TABLE IF NOT EXISTS `usuarios` (
  `username` varchar(100) NOT NULL,
  `gcmcode` varchar(500) NOT NULL,
  PRIMARY KEY (`username`)
) ENGINE=InnoDB;


Como podés ver, es una tabla de usuarios muy simple. El nombre de usuario es clave pero el gcmcode no, porque de vez en cuando el servicio de Google puede cambiarlo y nosotros debemos actualizarlo.

Vamos a crear 3 archivos php, uno para gestionar las llamadas a las funciones de nuestra aplicación servidor, otro para los usuarios y otro para los mensajes. Vamos a empezar por los usuarios. Para esto creamos un archivo llamado users.php, asi:


<?php
class users {
 
 function __construct() {
  $con = mysql_connect("NOMBRE_HOST", "NOMBRE_DB_USER", "DB_PASSWORD");  
  mysql_select_db("NOMBRE_DB_DATABASE");
 }
 
 public function saveUser($username, $gcmcode){
  $response = array("success" => "0");
  $sql  ="INSERT INTO usuarios (username, gcmcode) VALUES ('" . stripslashes($username) . "', '" . stripslashes($gcmcode) . "') 
    ON DUPLICATE KEY UPDATE gcmcode = '" . stripslashes($gcmcode)."' ; ";
  
  $result = mysql_query($sql);
  if(mysql_affected_rows() > 0)
   $response["success"] = "1";  
  
  return json_encode($response);
 }
 
 public function getUser($username){
  $response = array("success" => 0);
  
  $sql = "SELECT username, gcmcode
    FROM usuarios 
    WHERE username='".stripslashes($username)."';";
  $result = mysql_query($sql);
  if(mysql_num_rows($result) > 0){
   $data = mysql_fetch_array($result);
   
   return $data;
  } else 
   return false;  
 }
}
?>



Para empezar, en el constructor de la clase users configuramos la base de datos. Se debería tener los parámetros en un archivo de configuración a parte, pero para simplificar lo dejamos así. El método saveUser recibe el nombre de nuestro usuario en la aplicación y el código que nos dió Google. Como ven se intenta hacer un insert en la tabla de usuarios y en caso de que el usuario ya exista se renueva el código de Google. Esto es por lo que comentaba antes, Google de vez en cuando puede cambiar nuestro código usuario y debemos actualizarlo. El método getUser lo utilizaremos en el siguiente archivo y nos devolverá información sobre un determinado usuario.

Lo siguiente es el archivo destinado a gestionar los mensajes, el cual llamaremos gcm.php


<?php 
class gcm {
 
 function __construct() {
  $con = mysql_connect("NOMBRE_HOST", "NOMBRE_DB_USER", "DB_PASSWORD");
  mysql_select_db("NOMBRE_DB_DATABASE");
 }
 
 function sendMessageToPhone($collapseKey, $messageText, $username)
 {
  include_once 'users.php';
  $user = new users();
  $data = $user->getUser($username); 
  if($data != false){
  
   $apiKey = 'AQUI LA API KEY PARA SERVER QUE NOS DIO GOOGLE';
   
   $userIdentificador = $data["gcmcode"];
   
   $headers = array('Authorization:key=' . $apiKey);
   $data = array(
     'registration_id' => $userIdentificador,
     'collapse_key' => $collapseKey,
     'data.message' => $messageText);
  
   $ch = curl_init();
  
   curl_setopt($ch, CURLOPT_URL, "https://android.googleapis.com/gcm/send");
   if ($headers)
    curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
   curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
   curl_setopt($ch, CURLOPT_POST, true);
   curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
   curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
  
   $response = curl_exec($ch);
   $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
   if (curl_errno($ch)) {
    return 'fail';
   }
   if ($httpCode != 200) {
    return 'status code 200';
   }
   curl_close($ch);
   return $response;
  } else {
   return 'no user';
  }
 } 
}

?>

Otra vez en el constructor estamos inicializando la conexión a la base de datos. Y el método sendMessagetoPhone recibe tres parámetros. El primero es un identificador para indicarle a Google si debe colapsar o no los mensajes en caso de que hablen de un mismo tema. Si lanzas notificaciones sobre una misma noticia por ejemplo, colapsará los mensajes. Podes utilizar por ejemplo el id de la noticia, su permalink que actuará como identificador, en fin, algo único que sirve para identificar notificaciones del mismo tipo. El segundo es el mensaje que vamos a mandar y el tercero el nombre del usuario al que le enviamos el mensaje.

Este método lo que va a hacer es enviar al servidor de Google una petición para enviar un mensaje. En el apartado de registration_id, si queremos enviar a más de un dispositivo simplemente creamos un array con los ids de los usuarios de destino. Y el atributo data.message nos sirve para enviar el texto del mensaje. Si quisieramos enviar más datos como por ejemplo la fecha, añadiriamos el parámetro data.fecha, y si queremos, también el nombre del autor sería data.autor. Todo lo que querramos añadir a mayores debe ir precedido de "data.".

Finalmente este método nos devolverá el resultado de la petición, la cual puede fallar por diversos motivos. El como gestionemos los errores ya dependerá de cada uno.

El último archivo que nos queda por ver es el index.php, este servirá como entrada para llamar a todas los métodos que creamos antes.


<?php
include_once 'users.php';
include_once 'gcm.php';
$tag = $_POST['tag'];

switch ($tag){
 case 'usersave':
  $username = "";
  $gcmcode = "";
  if(isset($_POST['username']))
   $username = $_POST['username'];
  if(isset($_POST['gcmcode']))
   $gcmcode = $_POST['gcmcode'];
  
  $user = new users();
  echo $user->saveUser($username, $gcmcode);
  break;
 case 'sendmessage':
  $username = "";
  $message = "";
  $collapseKey = "";
  if(isset($_POST['message']))
   $message = $_POST['message'];
  if(isset($_POST['username']))
   $username = $_POST['username'];
  if(isset($_POST['collapsekey']))
   $collapseKey = $_POST['collapsekey'];
  $message = new gcm();
  echo $message->sendMessageToPhone($collapseKey, $message, $username);
  break;
 default:
  $response = array("success" => "0", "err" => "no tag");
  echo json_encode($response);
  break;
}
?>



El archivo es muy simple, simplemente recibe por post un parámetro tag que será el que nos diga que acción realizar. Si insertamos o actualizamos un nuevo usuario además debemos enviar el nombre de usuario y el código de registro para ese usuario en el servicio de Google.

Si lo que hacemos es enviar un mensaje le indicamos el nombre de usuario, el mensaje y el collapseKey que será por así decirlo como un identificador para mensajes que se agrupen por un mismo tema. Por ejemplo para una noticia en concreto. En este caso estamos asumiendo que solo vamos a enviar a un único usuario, pero si fueramos a enviar a más usuarios, deberiamos modificar el método para poder recibir todos los usuarios que quisieramos.

Bueno, ahora veremos la parte cliente en Android. Como registrar un usuario en el servicio de Google Cloud Messaging y como recibir mensajes y notificarlo.  Daremos de alta y baja a un usuario y prepararemos nuestra aplicación para recibir mensajes.


Como antes, debemos comprobar si tenemos instalado en nuestro SDK de Android la librería gcm.jar que nos facilitará casi todo el trabajo. Para ello, en Eclipse vamos a Window > Android SDK Manager. Aquí nos aparecerán todas las APIs y librerías que tenemos instaladas. Vamos a la sección Extras y si no está instalada la opción Google Cloud Messaging for Android Library la chequeamos e instalamos.




Una vez terminada la instalación, vamos a la carpeta donde tengamos instalado el SDK de Android y desde ahí hasta la carpeta \extras\google\gcm\gcm-client\dist y copiamos el archivo gcm.jar.

Ahora vamos a nuestro proyecto y en la carpeta libs pegamos este archivo. Hacemos click con el botón derecho del mouse sobre él y seleccionamos Build Path > Add to Build Path. Ahora ya podemos empezar a crear nuestro servicio para recibir notificaciones push.

Empezaremos primero por el archivo AndroidManifest.xml :


<permission android:name="nombre.del.package.de.nuestra.app.permission.C2D_MESSAGE" android:protectionLevel="signature" />
<uses-permission android:name="nombre.del.package.de.nuestra.app.permission.C2D_MESSAGE" />
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.VIBRATE"/>
<application>
<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="nombre.del.package.de.nuestra.app" />
         </intent-filter>
     </receiver>
  
     <service android:name=".GCMIntentService" />
</application>



Las primeras líneas son para indicarle al dispositivo que podrá recibir notificaciones del servicio de Google Cloud Messaging, además indica que solo nuestra aplicación (la que tiene nuestro package) podrá hacerlo. El siguiente permiso, GET_ACCOUNTS lo necesita Google para su servicio de mensajería. Después vienen permisos para internet y verificar el estado de la red, imprescindibles para un servicio de estas características. WAKE_LOCK permitirá "despertar" nuestro dispositivo si está "durmiendo". Y el último VIBRATE, es opcional, simplemente lo usareis si queréis que al notificar algo nuestro dispositivo vibre.

Después, dentro de la etiqueta application (miren que a esta etiqueta no le he puesto ningún atributo, ustedes dejen la que tengan en su app, simplemente es para que sepan donde poner lo siguiente) definimos un BroadcastReceiver el cual ya viene definido en la librería gcm.jar y hará todo el trabajo sucio, registrar nuestro usuario, recibir notificaciones. Únicamente debemos respetar el nombre del broadcast.

Por último, se define un Service que crearemos luego y que nos ayudará cuando registremos un usuario, cuando recibamos un mensaje o tengamos algún error.

Ahora que ya tenemos el AndroidManifest.xml aclarado vamos a ver como se registran y desregistran los usuarios en nuestra app. El método registerUser antes de nada verifica si existe en el dispositivo el registrarion id que nos da el servicio de Google Cloud Messaging para cada usuario. Si no existe ese código lo registramos con la ayuda de la librería gcm.jar, y si existe no hacemos nada, evitando el proceso de llamar al servicio de Google.

El proceso de desregistro es muy similar, verificamos si existe el código de registro de usuario y llamamos al método unregister de la librería gmc.jar.


private void registerUser(Context context){
  
        final String regId = GCMRegistrar.getRegistrationId(context);
        if (regId.equals("")) {
         GCMRegistrar.register(context, "SENDER_ID QUE VIMOS ANTERIORMENTE"); 
         Log.v("GCM", "Registrado");
        } else {
            Log.v("GCM", "Ya registrado");
        } 
 }
private void unregisterUser(Context context){
 final String regId = GCMRegistrar.getRegistrationId(context);
        if (!regId.equals("")) {
            GCMRegistrar.unregister(context);
        } else {
            Log.v("GCM", "Ya des-registrado");
        }
 }

Los dos métodos los pongo solos y no los ubico en ninguna activity porque ustedes deben decidir donde van. Por ejemplo, si la app no tiene login podrian poner el método de registro en el onCreate de la primera activity y así todos los usuarios recibirían notificaciones. Si tiene login se puede poner una vez el usuario se haya logado. O a lo mejor solo quieren activarlo y desactivarlo en un formulario donde el usuario lo indique expresamente, mediante las SharedPreference, por ejemplo.

El último paso que nos queda por realizar es crear el Service que comentamos antes en el archivo AndroidManifest.xml, para esto creamos una nueva clase llamada GCMIntentService y que heredará de GCMBaseIntentService.


public class GCMIntentService extends GCMBaseIntentService {

 public GCMIntentService(){
  super("SENDER ID QUE NOS DIO LA API DE GOOGLE");
 }
 
 @Override
 protected void onError(Context context, String errorId) {
  Log.d("GCM", "Error: " + errorId);
 }

 @Override
 protected void onMessage(Context context, Intent intent) {
     String msg = intent.getExtras().getString("msg");
     Log.d("GCM", "Mensaje: " + msg);
     notificarMensaje(context, msg);
 }

 @Override
 protected void onRegistered(Context context, String regId) {
  Log.d("GCM", "onRegistered: Registrado OK.");
   //En este punto tenemos que obtener el usuario donde lo tengas guardado.
   //Si no tenes un sistema de login y los usuarios son anónimos podes simplemente almacenar el regId
  String usuario = "";
  
    registrarUsuario(usuario, regId);

 }

 @Override
 protected void onUnregistered(Context context, String regId) {
  Log.d("GCM", "onUnregistered: Desregistrado OK.");
 }
 
 private void registrarUsuario(String username, String regId){
  Log.w("GCM", "registrarUsuario");
  Log.w("GCM", "username: " + username);
  Log.w("GCM", "regID: " + regId);
  try{
   
   ArrayList<namevaluepair> nameValuePairs = new ArrayList<namevaluepair>();
   nameValuePairs.add(new BasicNameValuePair("tag","usersave"));
   nameValuePairs.add(new BasicNameValuePair("username",username));
   nameValuePairs.add(new BasicNameValuePair("gcmcode",regId));
   
   HttpClient httpclient = new DefaultHttpClient();
   HttpPost httppost = new HttpPost("http://ip donde está alojado el servicio web/index.php");
   if(nameValuePairs != null)
    httppost.setEntity(new UrlEncodedFormEntity(nameValuePairs));
   
   ResponseHandler<string> responseHandler = new BasicResponseHandler();
   String res = httpclient.execute(httppost, responseHandler);
   
   Log.w("GCM", "RES: " + res);
   

  } catch(Exception e){
   Log.w("GCM", "ex: " + e.getMessage().toString());
  }
 }
 
 private void notificarMensaje(Context context, String msg)
 {
     String notificationService = Context.NOTIFICATION_SERVICE;
     NotificationManager notificationManager =(NotificationManager) context.getSystemService(notificationService);
  
     //Configuramos la notificación
     int icono = android.R.drawable.ic_launcher;
     CharSequence estado = "Has recibido un nuevo mensaje";
     long hora = System.currentTimeMillis();
  
     Notification notification = new Notification(icono, estado, hora);
     long[] vibrate = {100,100,200,300};
     notification.vibrate = vibrate;
  
     //Configuramos el Intent
     Context contexto = context.getApplicationContext();
     CharSequence titulo = "Nombre app - nuevo Mensaje";
     CharSequence descripcion = msg;
  
     Intent intent = new Intent(contexto, activitydestino.class);
     Bundle b = new Bundle();
     b.putInt("update", 1);
     intent.putExtra("android.intent.extra.INTENT", b);

     PendingIntent contIntent = PendingIntent.getActivity(contexto, 0, intent, android.content.Intent.FLAG_ACTIVITY_NEW_TASK);
  
    notifification.setLatestEventInfo(contexto, titulo, descripcion, contIntent);
  
    notification.flags |= Notification.FLAG_AUTO_CANCEL;
  
    notificationManager.notify(1, notification); }

}


Esta última parte es un poco más compleja, pero no mucho. Lo primero es mirar en el constructor, debe llamar al constructor padre pasándole el sender id que nos da Google, lo que en la web es el ID Project.

Despues tendremos cuatro eventos básicos que sobreescribimos para hacer lo que más se adapte a nuestra aplicación. Tenemos onError, que nos informa de algún error en el proceso. onRegistered y onUnregistered que se producen cuando un usuario se da de alta o baja del servicio de Google Cloud Messaging, en estos dos casos debemos dar de alta o baja al usuario en nuestro servidor. Y por último, onMessage, que se produce cada vez que llega un mensaje push.

Lo que hace el método registrarUsuario ya lo hemos visto otras veces, simplemente es una llamada al servicio web que hicimos y que envía el nombre de usuario, el código de Google Cloud Messaging y un tag para indicar la acción a realizar. A la hora de tomar el resultado de la acción no hacemos nada con el resultado, pero se podría o bien guardar en caso de error el usuario para volver a intentar registrar más tarde o bien des-registrar del servicio de Google al usuario y que al volver a entrar se repitiera el proceso de registro. El como, cuando y donde hacer esta parte ya les corresponde a ustedes y como hayan planteado el alta del usuario en el sistema.

El último método notificarMensaje simplemente recibe un mensaje y lo notifica al usuario. Si recordas, en el servicio web se mandaban parámetros con data.msg, data.fecha, data.autor... para tomarlos, en onMessage se utiliza: intent.getExtras().getString("msg"); Esto será para el caso de data.msg, y haces lo mismo para el resto sustituyendo msg por lo que corresponda.

A la hora de notificar hacemos uso de notificationManager, podemos configurar el icono, el texto de cabecera, le pasamos la descripción que será el mensaje que hemos recibido. Si queremos podemos hacer que vibre el teléfono. También podemos definir un Intent para que al pulsar en el mensaje abra la aplicación en un determinado punto y además en nuestro caso le pasamos un parámetro que en mi caso se llama update y su valor es 1, de esta forma puedo ahorrarle mucha batería y tráfico de datos al usuario si solo actualizo datos cuando yo se lo notifico.

Se podría enviar en los parámetros datos como un identificador y abrir la ficha de una noticia por ejemplo, o lo que a ustedes se les ocurra. Incluso desde el notificarMensaje podes hacer de forma totalmente silenciosa alguna actualización de datos y el usuario al entrar no tendría que esperar y siempre tendría los datos actualizados.

Por último, para empezar a enviar notificaciones para recibirlas en el dispositivo móvil, debes utilizar el servicio web que se presentó al comienzo, haciendo llamadas al método sendMessageToPhone de la clase gcm. Este lo podrías integrar en una web, por ejemplo cada vez que se publique una noticia que se llame. También en un chat, o incluso podes hacer llamadas a este servicio desde la misma aplicación móvil para tener nuestra app de mensajería.

Listo por el momento, les recuerdo que siempre pueden consultar el codigo de referencia que tenemos al descargarnos el SDK de Android, ahi tenemos un cliente y un servidor para poder mirar.

FUENTE: http://www.nosinmiubuntu.com/2012/12/notificaciones-push-en-android-ii.html 

2 comentarios:

  1. no consigo renombrar la aplicación y que siga funcionando... (cambio las referencias del manifest y escribo el nuevo nombre de la aplicación y nada..)

    ResponderEliminar
  2. podrias darme un ejemplo para el collapsekey

    ResponderEliminar