En el artículo anterior del curso hicimos una introducción al servicio Google Cloud Messaging (GCM), vimos cómo registrarnos y obtener la API Key necesaria para enviar mensajes y describimos a alto nivel la arquitectura que tendrá un sistema capaz de gestionar mensajería de tipo push a través de este servicio de Google. Este segundo artículo lo vamos a dedicar a la implementación de una aplicación web capaz de enviar mensajes o notificaciones push a dispositivos Android. En el próximo artículo veremos cómo desarrollar la aplicación Android cliente capaz de recibir estos mensajes.
Como ya viene siendo habitual en el curso, el sistema elegido para desarrollar la aplicación web será ASP.NET, utilizando C# como lenguaje, y SQL Server como base de datos.
Como ya comentamos en el artículo anterior, la aplicación web será responsable de las siguientes tareas:
- Almacenar y mantener el listado de dispositivos cliente que podrán recibir mensajes.
- Enviar los mensajes a los clientes a través del servicio GCM de Google.
En cuanto al punto 1, la aplicación deberá ser capaz de recibir los datos de registro de cada cliente que se «dé de alta» para recibir mensajes y almacenarlos en la base de datos. Esto lo haremos mediante la creación de un servicio web que exponga un método capaz de recoger y almacenar los datos de registro de un cliente. La aplicación Android se conectará directamente a este servicio web y llamará al método con sus datos identificativos para registrarse como cliente capaz de recibir notificaciones. Por supuesto que para esto se podría utilizar cualquier otro mecanismo distinto a servicios web, por ejemplo una simple petición HTTP al servidor pasando los datos como parámetros, pero no nos vendrá mal para seguir practicando con servicios web en android, que en este caso será de tipo SOAP.
Por su lado, el punto 2 lo resolveremos a modo de ejemplo con una página web sencilla en la que podamos indicar el nombre de usuario de cualquiera de los dispositivos registrados en la base de datos y enviar un mensaje de prueba a dicho cliente.
Vamos a empezar creando la base de datos, aunque no nos detendremos mucho porque ya vimos el procedimiento por ejemplo en el primer artículo dedicado a servicios web SOAP. Tan sólo decir que crearemos una nueva base de datos llamada DBUSUARIOS, que tendrá dos campos: NombreUsuario y CodigoC2DM, el primero de ellos destinado a almacenar un nombre de usuario identificativo de cada cliente registrado, y el segundo para almacenar el RegistrationID de GCM recibido desde dicho cliente a través del servicio web (recomiendo consultar el artículo anterior para entender bien todo este «protocolo» requerido por GCM).
Una vez creada la base de datos vamos a crear en Visual Studio 2010 un nuevo proyecto C# de tipo «ASP.NET Web Application» al que llamaremos «GCMServer«, y añadiremos a este proyecto un nuevo componente de tipo «Web Service» llamado «ServicioRegistroGCM.asmx». Todo este procedimiento también se puede consultar en el artículo sobre servicios web SOAP en Android.
Añadiremos un sólo método web al servicio, al que llamaremos RegistroCliente() y que recibirá como hemos comentado 2 parámetros: el nombre de usuario y el ID de registro del cliente en GCM. El método se limitará a realizar el INSERT o UPDATE correspondiente con estos dos datos en la base de datos que hemos creado de usuarios.
[WebMethod] public int RegistroCliente(string usuario, string regGCM) { SqlConnection con = new SqlConnection( @"Data Source=SGOLIVERPC\SQLEXPRESS;Initial Catalog=DBUSUARIOS;Integrated Security=True"); con.Open(); string cod = CodigoCliente(usuario); int res = 0; string sql = ""; if (cod == null) sql = "INSERT INTO Usuarios (NombreUsuario, CodigoC2DM) VALUES (@usuario, @codigo)"; else sql = "UPDATE Usuarios SET CodigoC2DM = @codigo WHERE NombreUsuario = @usuario"; SqlCommand cmd = new SqlCommand(sql, con); cmd.Parameters.Add("@usuario", System.Data.SqlDbType.NVarChar).Value = usuario; cmd.Parameters.Add("@codigo", System.Data.SqlDbType.NVarChar).Value = regGCM; res = cmd.ExecuteNonQuery(); con.Close(); return res; }
El código es sencillo, pero ¿por qué es necesario considerar el caso del UPDATE? Como ya advertimos en el artículo anterior, el servidor GCM puede en ocasiones refrescar (actualizar) el ID de registro de un cliente comunicándoselo de nuevo a éste, por lo que a su vez la aplicación cliente tendrá que hacer también la misma actualización contra la aplicación web. Para ello, el cliente simplemente volverá a llamar al método RegistroCliente() del servicio web pasando el mismo nombre de usuario pero con el ID de registro actualizado. Para saber si el cliente está ya registrado o no el método se apoya en un método auxiliar llamado CodigoCliente() que realiza una búsqueda de un nombre de usuario en la base de datos para devolver su ID de registro en caso de encontrarlo. El código de este método es igual de sencillo que el anterior:
public string CodigoCliente(string usuario) { SqlConnection con = new SqlConnection( @"Data Source=SGOLIVERPC\SQLEXPRESS;Initial Catalog=DBUSUARIOS;Integrated Security=True"); con.Open(); string sql = "SELECT CodigoC2DM FROM Usuarios WHERE NombreUsuario = @usuario"; SqlCommand cmd = new SqlCommand(sql, con); cmd.Parameters.Add("@usuario", System.Data.SqlDbType.NVarChar).Value = usuario; string cod = (String)cmd.ExecuteScalar(); con.Close(); return cod; }
Con esto ya tendríamos implementado nuestro servicio web para el registro de clientes.
Para el envío de los mensajes utilizaremos directamente la página «Default.aspx» creada por defecto al generar el proyecto de Visual Studio. Modificaremos esta página para añadir tan sólo un cuadro de texto donde podamos introducir el nombre de usuario asociado al cliente al que queremos enviar un mensaje, un botón «Enviar» con el realizar el envío, y una etiqueta donde mostrar el estado del envío. Quedaría algo como lo siguiente:
El botón de envío, realizará una búsqueda en la base de datos del nombre de usuario introducido, recuperará su Registration ID y enviará un mensaje de prueba con la fecha-hora actual.
protected void Button3_Click(object sender, EventArgs e) { ServicioRegistroGCM svc = new ServicioRegistroGCM(); string codUsuario = svc.CodigoCliente(TxtUsuario.Text); bool res = enviarMensajePrueba(codUsuario); if (res == true) LblResultadoMensaje.Text = "Envío OK"; else LblResultadoMensaje.Text = "Envío NOK"; }
Como vemos en el código, toda la lógica de envío de mensajes la he encapsulado en el método auxiliar enviarMensajePrueba() para poder centrarme ahora en ella. En este método es donde vamos a hacer realmente uso de la API del servicio de Google Cloud Messaging, y por ello antes de ver la implementación vamos a hablar primero de las distintas opciones de esta API.
Todas las llamadas a la API de GCM para enviar mensajes se realizan mediante peticiones HTTP POST a la siguiente dirección:
[box type=»info» border=»full»]https://android.googleapis.com/gcm/send[/box]
La cabecera de esta petición debe contener dos datos esenciales. Por un lado debemos indicar la API Key que generamos en el primer artículo (atributo Authorization), y por otro lado el formato del contenido (en este caso, los parámetros de la API) que vamos a incluir con la petición (atributo Content-Type). GCM permite formatear los datos como JSON (para lo que habría que indicar el valor «application/json«) o como texto plano (para lo que debemos utilizar el valor «application/x-www-form-urlencoded«). En nuestro caso de ejemplo utilizaremos la segunda opción.
Dado que hemos elegido la opción de texto plano, los distintos datos del contenido se formatearán como parámetros HTTP con el formato tradicional, es decir, tendremos que construir una cadena de la forma «param1=valor1¶m2=valor2&…«.
Entre los distintos datos que podemos incluir hay tan solo uno obligatorio, llamado registration_id, que debe contener el ID de registro del cliente al que se le va a enviar el mensaje. A parte de éste también podemos incluir los siguientes parámetros opcionales:
- delay_while_idle. Hace que el servidor de GCM no envíe el mensaje al dispositivo mientras éste no se encuentre activo.
- time_to_live. Indica el tiempo máximo que el mensaje puede permanecer en el servidor de GCM sin entregar mientras el dispositivo está offline. Por defecto 4 semanas. Si se especifica algún valor también habrá que incluir el parámetro siguiente, collapse_key.
- collapse_key. Éste lo explicaré con un ejemplo. Imaginad que activamos el parámetro delay_while_idle y que el dispositivo que debe recibir el mensaje permanece inactivo varias horas. Si durante esas horas se generaran varias notificaciones hacia el dispositivo, estos mensajes se irían acumulando en el servidor de GCM y cuando el dispositivo se activara le llegarían todos de golpe. Esto puede tener sentido si cada mensaje contiene información distinta y relevante, pero ¿y si los mensajes simplemente fueran por ejemplo para decirle al dispositivo «Tienes correo nuevo»? Sería absurdo entregar en el varias notificaciones de este tipo en el mismo instante. Pues bien, para esto se utiliza el parámetro collapse_key. A este parámetro podemos asignarle como valor cualquier cadena de caracteres, de forma que si se acumulan en el servidor de GCM varios mensajes para el mismo dispositivo y con la misma collapse_key, al dispositivo sólo se le entregará el último de ellos cuando éste se active, descartando todos los demás.
- data.<nombre_dato>. Se pueden incluir tantos parámetros de este tipo como queramos, para incluir cualquier otra información que queramos en el mensaje. Por ejemplo podríamos pasar los datos de un nuevo correo recibido con dos parámetros como los siguientes: «data.emisor=aaa@gmail.com«, y «data.asunto=pruebagcm«. Tan solo recordad preceder el nombre de los datos con el prefijo «data.«.
Una vez formateada convenientemente la cabecera y contenido de la petición HTTP, y realizada ésta a la dirección indicada anteriormente, podemos obtener diferentes respuestas dependiendo del resultado de la petición. Diferenciaremos los distintos resultados por el código de estado HTTP recibido en la respuesta:
- 200. El mensaje se ha procesado correctamente, en cuyo caso se devuelve en los datos un parámetro «id=» con el código del mensaje generado.
- 401. Ha fallado la autenticación de nuestra aplicación web contra los servidores de GCM. Normalmente significará algún problema con la API Key utilizada.
- 500. Se ha producido un error al procesarse el mensaje. En este caso la respuesta incluirá en su contenido un parámetro «Error=» que indicará el código de error concreto devuelto por GCM.
- 501. El servidor de GCM no está disponible temporalmente.
Y eso es todo, largo de contar pero sencillo en el fondo. Veamos cómo podemos implementar esto en C#, y para ello vamos a ver el código del método que dejamos antes pendiente, enviarMensajePrueba(), y justo después lo comentamos.
private static bool enviarMensajePrueba(String registration_id) { String GCM_URL = @"https://android.googleapis.com/gcm/send"; string collapseKey = DateTime.Now.ToString(); Dictionary data = new Dictionary(); data.Add("data.msg", HttpUtility.UrlEncode("Prueba. Timestamp: " + DateTime.Now.ToString())); bool flag = false; StringBuilder sb = new StringBuilder(); sb.AppendFormat("registration_id={0}&collapse_key={1}", registration_id, collapseKey); foreach (string item in data.Keys) { if (item.Contains("data.")) sb.AppendFormat("&{0}={1}", item, data[item]); } string msg = sb.ToString(); HttpWebRequest req = (HttpWebRequest)WebRequest.Create(GCM_URL); req.Method = "POST"; req.ContentLength = msg.Length; req.ContentType = "application/x-www-form-urlencoded"; string apiKey = "AIzaSyCJ7QSQAznAmhDzNTLSUE6uX9aUfr9-9RI"; req.Headers.Add("Authorization:key=" + apiKey); using (StreamWriter oWriter = new StreamWriter(req.GetRequestStream())) { oWriter.Write(msg); } using (HttpWebResponse resp = (HttpWebResponse)req.GetResponse()) { using (StreamReader sr = new StreamReader(resp.GetResponseStream())) { string respData = sr.ReadToEnd(); if (resp.StatusCode == HttpStatusCode.OK) // OK = 200 { if (respData.StartsWith("id=")) flag = true; } else if (resp.StatusCode == HttpStatusCode.InternalServerError) // 500 Console.WriteLine("Error interno del servidor, prueba más tarde."); else if (resp.StatusCode == HttpStatusCode.ServiceUnavailable) // 503 Console.WriteLine("Servidor no disponible temporalmente, prueba más tarde."); else if (resp.StatusCode == HttpStatusCode.Unauthorized) // 401 Console.WriteLine("La API Key utilizada no es válida."); else Console.WriteLine("Error: " + resp.StatusCode); } } return flag; }
Como vemos el método recibe directamente como parámetro el Registration ID del cliente al que se va a enviar el mensaje. En primer lugar configuro todos los parámetros que pasará en la llamada a la API, que en este caso de ejemplo tan sólo serán, además del registration_id ya comentado, el colapse_key, y una dato adicional que llamaré «data.msg» (recordemos el prefijo «data.» obligatorio para este tipo de datos adicionales) con un mensaje de prueba que contenga la fecha/hora actual. Toda la cadena con estos parámetros la construyo utilizando un objeto StringBuilder. Lo único reseñable hasta ahora sería la forma de añadir el parámetro adicional data.msg, que lo hago mediante la creación de un objeto Dictionary y su método add() para añadir el dato, para poco después generar la cadena final recorriendo este diccionario en un bucle foreach. En este caso no sería necesaria toda esta parafernalia dado que sólo vamos a añadir un dato adicional, pero lo he dejado así para que tengáis un ejemplo de «patrón» mediante el cual podeis añadir más de un dato adicional de una forma sencilla y organizada.
Una vez creada la cadena de parámetros y datos que incluiremos como contenido de la petición creamos dicha petición como un objeto HttpWebRequest indicando la URL del servicio. Indicamos que la petición será de tipo POST asignando la propiedad Method, y configuramos la cabecera con los dos datos que ya hemos comentado antes en el artículo (Authorization y Content-Type). El primero de ellos al ser «personalizado» debemos añadirlo utilizando el método Add() de la colección Headers de la petición. En cambio para el segundo existe una propiedad del objeto HttpWebRequest con la que podemos establecerlo directamente, llamada ContentType. Hecho esto, tan sólo nos queda añadir el contenido a la petición, lo que conseguimos obteniendo el stream de escritura de la petición mediante GetRequestStream() y escribiendo en él nuestra cadena de parámetros mediante el método Write().
Seguidamente vamos a ejecutar la petición y a obtener la respuesta como objeto HttpWebResponse mediante una llamada a GetResponse(). Por último, obtenemos el código de estado HTTP de la respuesta mediante la consulta a su propiedad StatusCode, y los datos asociados obteniendo el stream de lectura de la respuesta mediante GetResponseStream() y el método ReadToEnd() para leer todo el contenido. Evaluando estos dos datos determinamos fácilmente el resultado del envío según la información ya comentada antes en el artículo.
Y con esto habríamos terminado la implementación del servidor. Haremos las pruebas pertinentes y mostraré el resultado cuando implementemos la aplicación Android cliente en el próximo artículo.
Puedes consultar y/o descargar el código completo de los ejemplos desarrollados en este artículo accediendo a la página del curso en GitHub.
Actualización: Existe una librería .NET/Mono llamada PushSharp capaz de facilitarnos la vida a la hora de enviar notificaciones push a dispositivos Android (y también iOS, Windows Phone y Blackberry).
18 comentarios
[…] los dos anteriores (I y II) artículos del curso hemos hablado sobre el servicio Google Cloud Messaging y hemos visto como […]
El código fuente esta listo para compilarlo? porque a mi no me funciona… :S
hola que tal espero que andes de lo mejor, el manual que has creado es genial, durante estos dos dias he estado checando sobre Google Cloud Messaging pero me gustaría saber como se implementaria un servidor que haga lo que explicas en este post pero utilizando apache y php, he estado buscando algo al respecto pero la verdad no he encontrado nada, espero que me puedas ayudar y disculpa las molestias, saludos :)
macvirgo14 eso mismo pregunté y como no encontré respuesta lo implementé. No es muy complicado, espero que lo disfrutemos.
http://goo.gl/apG9z
hey gracias por el dato Fernando, ya había visto antes esa libreria pero la verdad senti que que era algo complicado al menos para mi ya que realmente no soy un experto en php y así que segui navegando por la web y encontre este sencillo código que utiliza algo así llamado curl y me funciono creo que es mas sencillo de implementar que la libreria que compartes pero la cuestión aqui tambien seria ver cuales son las ventajas y desventajas de cada uno aqui te dejo el link por si gustas revisar saludos http://stackoverflow.com/questions/11242743/gcm-with-php-google-cloud-messaging
Ante todo muchisimas gracias por este EXCELENTE curso. He intentado implementar tu ejemplo, pero el servicio de hosting que tengo contratado, me limita el uso del puerto 443. Segun entiendo (que no es mucho) es necesario para poder hacer el post a la url «https://android.googleapis.com/gcm/send». Podrías indicarme si existe algun truco, método o configuración para poder hacer el llamado al servicio con un puerto diferente?
Gracias de antemano y sigue con tus inmejorables post, ya que como yo, muchos aprendemos gracias a ti.
Hola, Exelente tutorial no solo este articulo sino todos los anteriores, pero tendo un problema estoy trabajando con java y servidor tomcat, para el ejemplo requiero herramientas de punto net con los cuales no estoy muy familiarizado, no se si podrias complementar el tutorial con tomcat para el servidor.
gracias por tus tutoriales.
Saludos, podria alquien decirme cual es la longitud y formato del registro que retorna GCMRegistrar.register(this, senderid)? es que, una vez guardado dicho valor, en mi servidor, e intentar conectarme al servidor de google, por medio de la url «https://android.googleapis.com/gcm/send», me retorna InvalidRegistration.
He revisado una y otra vez el valor generado de regId, en el metodo onRegistered de la clase GCMIntentService, y es el mismo que le envio al servidor de google.
Gracias de antemano
Saludos, podrian por favor indicarme como puedo cambiar la imagen que aparece en mi notificacion.
si no les funciona https://android.googleapis.com/gcm/send pueden usar http://android.googleapis.com/gcm/send
Hola , ¿qué tal? Saludos y buen manual ante todo. Tengo un problema, la aplicación solo me recibe una notificación. Si no la abro y me llega otra me machaca la anterior. ¿Le pasa a alguien más? Decir también que estoy trabajando con PhoneGap, aunque no creo que deba influir. Un saludo
[…] último el punto 3 lo implementaremos mediante la conexión al servicio web SOAP que creamos en el apartado anterior, sirviéndonos para ello de la librería ksoap2, tal como ya describimos en el capítulo sobre […]
Artículo m uy interesante, la verdad es que ya uso PushSharp para las notificaciones para iOS y con este artículo he entendido mejor como implementar las de Android.
Recomiendo esta librería, es fácil de implementar y así puedes gestionar la notificaciones para cualquier dispositivo!!
Buenos dias , alguuien sabe como implementar esto en java con Tomcat? Gracias buen tuto….
Hola, alguien sabe como puedo especificar la ruta para la conexión de sql porque llevo intentándolo toda la tarde y no hay manera. Que ruta hay que poner en Data Source?
sqlConnection con =
new SqlConnection(
@»Data Source=SGOLIVERPC\SQLEXPRESS;Initial Catalog=DBUSUARIOS;Integrated Security=True»);
Una consulta…la cantidad de caracteres que podemos enviar en el mensaje de push a un usuario?
gracias!
Hola amigo,
Excelente tus manuales.
Tengo un problema en mi servicio web, cuando se envía el parametro registerID de GCM por el metodo que lo inserta, éste trunca la cadena por lo que solo se almacena una parte del refgisterID en la base de datos.
La pregunta es: En el servicio web tengo que configurar algún parámetro para que no trunque el tamanio de los caracteres?
Espero me haya explicado correctamente.
Saludos
Quisiera saber cómo leer un mensaje gcm push necesito su ayuda… gracias