Olá povo,
Depois de um bom tempo sem postar, resolvi voltar com um tópico muito bacana: Google Cloud Messaging, ou simplesmente GCM. Esse serviço gratuito permite que sua aplicação web envie mensagens de notificação para sua aplicação Android. Nesse tutorial vou tentar mostrar como aplicar esse recurso na sua aplicação.
Cada aplicação deve ter um ID associado. Com esse ID, a aplicação solicita um token para o servidor do GCM, esse token é único por dispositivo+aplicação. De posse desse token, a aplicação Android deve enviar esse valor para a aplicação servidora. Esse token deve ser armazenado de alguma forma no servidor, pois quando ele precisar/desejar enviar uma mensagem, o fará através de uma requisição via POST para o servidor do GCM que se encarregará de despachá-las para os dispositivos Android.
Nada muito complexo não é? :) Agora vamos para a prática!
Gerando as chaves de acesso
Acesse o
Google Developers Console e crie um novo projeto. Precisaremos do número do projeto para podermos registrar a app+device no servidor do GCM.
Uma vez criado o projeto, selecione a opção
APIs dentro do menu
APIs & auth. Agora, habilite o Google Cloud Messaging.
Agora vamos gerar o SHA1 que será usado para gerar a chave para acessarmos o serviço do GCM. Digite o comando abaixo na pasta bin do JDK (Java Development Kit) (ou em qualquer lugar se esse caminho estiver no PATH do seu sistema operacional).
keytool -list -v -keystore ~/.android/debug.keystore
Aqui, estou usando o debug.keystore que por padrão fica no diretório usuário/.android (no Windows seria C:\Users\usuario). O próximo passo é criar duas chaves, uma para ser usada pela app Android e outra para app Web. Para isso, selecione a opção
Credentials no menu
APIs & auth. Clique na opção
Create new key e selecione
Android key. Na janela que for exibida, digite o SHA1 gerado pelo comando keytool, seguido de ";" e o pacote do projeto.
Exemplo:
41:F7:05:0F:72:29:99:28:55:BA:4B:79:AB:27:BC:7A:62:03:85:2C;ngvl.android.exemplogcm
Clique novamente em
Create new key e selecione
Server key. Aqui podemos filtrar o uso dessa chave por IP. Na prática utilizaríamos o IP real do servidor, mas para esse exemplo, não usaremos. É só confirmar para criar a nova chave.
Aplicação Cliente
Para a aplicação cliente primeiro temos que adicionar a referência ao projeto do Google Play Services, disponível na pasta ANDROID_SDK/extras/google/google_play_services/libproject. Depois adicione a dependência ao seu projeto.
No AndroidManifest.xml do seu projeto você deve adicionar as seguintes informações:
<manifest
package="ngvl.android.exemplogcm"
...>
<uses-permission android:name=
"com.google.android.c2dm.permission.RECEIVE"/>
<uses-permission android:name=
"android.permission.INTERNET"/>
<uses-permission android:name=
"android.permission.GET_ACCOUNTS"/>
<uses-permission android:name=
"android.permission.WAKE_LOCK"/>
<permission android:name=
"ngvl.android.exemplogcm.permission.C2D_MESSAGE"
android:protectionLevel="signature"/>
<uses-permission android:name=
"ngvl.android.exemplogcm.permission.C2D_MESSAGE"/>
<application
...>
...
<receiver
android:name=
"ngvl.android.exemplogcm.GcmBroadcastReceiver"
android:permission=
"com.google.android.c2dm.permission.SEND">
<intent-filter>
<action
android:name=
"com.google.android.c2dm.intent.RECEIVE"/>
<category
android:name="ngvl.android.exemplogcm"/>
</intent-filter>
</receiver>
<service
android:name=
"ngvl.android.exemplogcm.GcmIntentService"/>
<meta-data
android:name="com.google.android.gms.version"
android:value="@integer/google_play_services_version"/>
</application>
</manifest>
Basicamente, adicionamos algumas permissões:
- Para receber mensagens do GCM;
- Acessar internet;
- Ter acesso às contas cadastradas do aparelho (para usar o GCM, temos que ter uma conta Google criada no aparelho);
- Wake Lock para ligar a tela quando chegar uma notificação;
Além disso, declaramos um Broadcast Receiver que recebe as mensagens e delega o tratamento para o Intent Service que também adicionamos.
Abaixo temos o código do BroadcastReceiver. Ele é um WakefulBroadcastReceiver para evitar que o aparelho apague a tela enquanto ele está executando, que nesse caso é receber a notificação. Quando uma mensagem GCM é recebida esse componente é disparado, e então delega o tratamento para o GcmIntentService.
public class GcmBroadcastReceiver
extends WakefulBroadcastReceiver {
@Override
public void onReceive(Context context,
Intent intent) {
ComponentName comp = new ComponentName(
context.getPackageName(),
GcmIntentService.class.getName());
startWakefulService(
context, (intent.setComponent(comp)));
setResultCode(Activity.RESULT_OK);
}
}
O IntentService é um serviço especial do Android onde cada chamada ao mesmo é empilhada e executada sequencialmente em uma Thread separada. A requisição é tratada no método onHandleIntent e nele checamos se a mensagem que estamos recebendo é uma mensagem de dados (o GCM pode enviar mensagens de erro). Se for uma mensagem de dados, disparamos uma notificação.
public class GcmIntentService extends IntentService {
public static final int NOTIFICATION_ID = 1;
private NotificationManager mNotificationManager;
private NotificationCompat.Builder builder;
public GcmIntentService() {
super("GcmIntentService");
}
@Override
protected void onHandleIntent(Intent intent) {
Bundle extras = intent.getExtras();
GoogleCloudMessaging gcm =
GoogleCloudMessaging.getInstance(this);
String messageType = gcm.getMessageType(intent);
if (!extras.isEmpty()) {
if (GoogleCloudMessaging.
MESSAGE_TYPE_MESSAGE.equals(messageType)) {
sendNotification("Mensagem: " +
extras.toString());
}
}
GcmBroadcastReceiver.completeWakefulIntent(intent);
}
private void sendNotification(String msg) {
mNotificationManager = (NotificationManager)
this.getSystemService(
Context.NOTIFICATION_SERVICE);
PendingIntent contentIntent =
PendingIntent.getActivity(
this, 0, new Intent(), 0);
NotificationCompat.Builder mBuilder =
new NotificationCompat.Builder(this)
.setSmallIcon(R.drawable.ic_launcher)
.setContentTitle("GCM Notification")
.setStyle(new NotificationCompat.BigTextStyle()
.bigText(msg))
.setContentText(msg);
mBuilder.setContentIntent(contentIntent);
mNotificationManager.notify(
NOTIFICATION_ID, mBuilder.build());
}
}
Logo abaixo, criei uma classe utilitária para realizar as operações com o GCM. O método checkPlayService verifica se o google play está instalado, atualizado e se existe uma conta google criada no aparelho. Já o método getRegistrationId obtém o chave de acesso que o GCM retornou e que no nosso exemplo, estamos armazenando em uma SharedPreference. Isso está sendo feito no método storeRegistrationId.
O método registerInBackground tenta obter a chave de acesso do GCM usando uma AsyncTask. Ou seja, em uma Thread separada. Quando obtermos essa chave, a enviamos para nossa aplicação web e salvamos na SharedPreference.
Nesse exemplo, estamos enviando essa chave via requisição GET para o servidor. Obviamente, isso deve ser melhorado em uma aplicação de produção.
public class GcmHelper {
public static final String
EXTRA_MESSAGE = "message";
public static final String
PROPERTY_REG_ID = "registration_id";
private static final String
PROPERTY_APP_VERSION = "appVersion";
private static final String
SENDER_ID = "ID_DA_SUA_APP";
private static final String
IP = "IP_DO_SERVIDOR";
public static boolean checkPlayServices(
Activity activity, int requestCode) {
int resultCode = GooglePlayServicesUtil
.isGooglePlayServicesAvailable(activity);
if (resultCode != ConnectionResult.SUCCESS) {
if (GooglePlayServicesUtil
.isUserRecoverableError(resultCode)) {
GooglePlayServicesUtil.getErrorDialog(
resultCode, activity, requestCode).show();
} else {
Toast.makeText(activity,
"Dispositivo não suportado.",
Toast.LENGTH_SHORT).show();
activity.finish();
}
return false;
}
return true;
}
public static String getRegistrationId(
Context context) {
final SharedPreferences prefs =
getGCMPreferences(context);
String registrationId =
prefs.getString(PROPERTY_REG_ID, "");
if (registrationId.isEmpty()) {
return "";
}
int registeredVersion =
prefs.getInt(PROPERTY_APP_VERSION,
Integer.MIN_VALUE);
int currentVersion = getAppVersion(context);
if (registeredVersion != currentVersion) {
return "";
}
return registrationId;
}
private static void storeRegistrationId(
Context context, String regId) {
final SharedPreferences prefs =
getGCMPreferences(context);
int appVersion = getAppVersion(context);
SharedPreferences.Editor editor = prefs.edit();
editor.putString(PROPERTY_REG_ID, regId);
editor.putInt(PROPERTY_APP_VERSION, appVersion);
editor.commit();
}
public static void registerInBackground(
final Context ctx,
final GoogleCloudMessaging gcm,
final RegisterListener listener) {
new AsyncTask<Void, Void, String>() {
@Override
protected String doInBackground(
Void... params) {
String regid = null;
try {
regid = gcm.register(SENDER_ID);
sendRegistrationIdToBackend(regid);
storeRegistrationId(ctx, regid);
} catch (IOException ex) {
ex.printStackTrace();
}
return regid;
}
@Override
protected void onPostExecute(String msg) {
listener.onRegisterComplete(msg);
}
}.execute();
}
private static void sendRegistrationIdToBackend(
String key) throws IOException {
URL url = new URL(
"http://"+ IP +"/SeuServer/registrar?token="+ key);
HttpURLConnection conexao =
(HttpURLConnection)url.openConnection();
conexao.connect();
}
private static SharedPreferences
getGCMPreferences(Context context) {
return context.getSharedPreferences(
"GDE", Context.MODE_PRIVATE);
}
private static int getAppVersion(Context context) {
try {
PackageInfo packageInfo =
context.getPackageManager()
.getPackageInfo(context.getPackageName(), 0);
return packageInfo.versionCode;
} catch (NameNotFoundException e) {
throw new RuntimeException(e);
}
}
public interface RegisterListener {
void onRegisterComplete(String s);
}
}
A Activity né bem simples. Ela checa se o device suporta o Google Play (e se está atualizado), tenta obter a chave de acesso do GCM e depois a envia para o servidor.
public class ClientActivity extends Activity
implements RegisterListener {
private static final int REQUEST_CODE_GOOGLEPLAY = 0;
GoogleCloudMessaging gcm;
String regid;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_client);
if (GcmHelper.checkPlayServices(
this, REQUEST_CODE_GOOGLEPLAY)) {
gcm = GoogleCloudMessaging.getInstance(this);
regid = GcmHelper.getRegistrationId(this);
if (regid.isEmpty()) {
GcmHelper.registerInBackground(this, gcm, this);
}
} else {
Log.i("NGVL", "Google Play não instalado.");
}
}
@Override
public void onRegisterComplete(String s) {
Toast.makeText(this, "Registered: "+ s,
Toast.LENGTH_LONG).show();
}
}
Aplicação Servidora
Você pode usar qualquer tecnologia do lado do servidor para usar o GCM. Aqui vou usar um Servlet Java para receber o token do cliente Android e enviar mensagens para o mesmo. Aqui só estou salvando um device. Você terá que fazer a sua lógica para salvar vários.
@WebServlet("/gcm")
public class GcmServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
protected void doGet(
HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
PrintWriter writer = response.getWriter();
writer.print("Current key:"+ retrieveToken());
String key = request.getParameter("token");
if (key != null && !key.isEmpty()){
saveToken(key);
response.getWriter().print("\nRegistrado:"+ key);
}
}
protected void doPost(
HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
String msgJson =
"{ \"data\": { \"mensagem\":\""+
request.getParameter("msg") +"\"}, "+
"\"registration_ids\": [ \""+ retrieveToken() +
"\" ] }";
byte[] data = msgJson.getBytes("UTF-8");
URL url = new URL(
"https://android.googleapis.com/gcm/send");
HttpsURLConnection conexao =
(HttpsURLConnection)url.openConnection();
conexao.addRequestProperty("Authorization",
"key="+"sua_chave_web_aqui");
conexao.addRequestProperty(
"Content-Type", "application/json");
conexao.setRequestMethod("POST");
conexao.setDoOutput(true);
conexao.setUseCaches(false);
conexao.connect();
OutputStream os = conexao.getOutputStream();
os.write(data);
os.close();
PrintWriter writer = response.getWriter();
writer.print("Trying to send: "+ msgJson);
if (conexao.getResponseCode() ==
HttpURLConnection.HTTP_OK){
writer.print("\nDONE");
} else {
writer.print(
"\nERRO:"+ conexao.getResponseCode() +" - "+
conexao.getResponseMessage());
}
}
private void saveToken(String key)
throws IOException {
String serverDir =
getServletContext().getRealPath("/");
File f = new File(serverDir, "devices");
if (!f.exists())
f.createNewFile();
FileOutputStream fos = new FileOutputStream(f);
BufferedWriter out = new BufferedWriter(
new FileWriter(f, true));
out.write(key);
out.close();
fos.close();
}
private String retrieveToken() throws IOException{
String serverDir =
getServletContext().getRealPath("/");
File f = new File(serverDir, "devices");
if (f.exists()){
InputStream is = new FileInputStream(f);
BufferedReader rd = new BufferedReader(
new InputStreamReader(is));
String line = rd.readLine();
rd.close();
is.close();
return line;
}
return null;
}
}
Para fins de exemplo, o método GET desse servidor está recebendo o token do cliente. Aqui só estou armazenando 1 única chave em um arquivo TXT, mas na prática, devemos armazenar isso em um banco de dados. Para enviar uma mensagem para o Android, basta abrir a página abaixo, digitar a mensagem e clicar em enviar.
<html>
<head>
<title>Exemplo GCM</title>
</head>
<body>
<form method="post" action="gcm">
Mensagem:<input type="text" name="msg"><br>
<input type="submit" value="OK">
</form>
</body>
</html>
Qualquer dúvida, deixem seus comentários.
4br4ç05,
nglauber