Create an Instant Messaging app using Google Cloud Messaging (GCM)
DownloadKeywords: Google Cloud Messaging Google Play services ContentProvider SQLiteOpenHelper BroadcastReceiver NotificationManager SimpleCursorAdapter CursorLoader ActionBar DialogFragment ListFragment ViewBinder ContentResolver PreferenceFragment Google App Engine Google Plugin JPA Servlet
Contents- Overview
- Integrating Google Play Services
- Create an API project
- Obtain an API Key
- Create a new Eclipse Android project
- The Android Manifest file
- The Application class
- The Data Model
- The Content Provider
- GCM Utility class
- Server Utility class
- The GCM BroadcastReceiver
- Main Activity
- Add Contact DialogFragment
- Messages ListFragment
- Chat Activity
- Settings screen
- Install the Google Plugin for Eclipse
- Create new Web Application Project
- JPA model
- Servlets
- Deploy to App Engine
- Testing the app
10. GCM Utility class
GCM server needs registration ID to deliver message to a device. The registration ID identifies the device and application, as well as which servers are allowed to send messages. So to send or receive messages, you first need to get a registration ID.The official docs explains in detail how to use GoogleCloudMessaging API to register the application for GCM and obtain the registration ID.
In short, we get an instance of GoogleCloudMessaging and invoke its register(senderID) method where senderID is the project number we obtained earlier. This should be done asynchronously (not on the UI thread).
new AsyncTask<Void, Void, Boolean>() { @Override protected Boolean doInBackground(Void... params) { long backoff = BACKOFF_MILLI_SECONDS + random.nextInt(1000); for (int i = 1; i <= MAX_ATTEMPTS; i++) { Log.d(TAG, "Attempt #" + i + " to register"); try { if (gcm == null) { gcm = GoogleCloudMessaging.getInstance(ctx); } String regid = gcm.register(Common.getSenderId()); // You should send the registration ID to your server over HTTP, // so it can use GCM/HTTP or CCS to send messages to your app. ServerUtilities.register(Common.getPreferredEmail(), regid); // Save the regid - no need to register again. setRegistrationId(regid); return Boolean.TRUE; } catch (IOException ex) { Log.e(TAG, "Failed to register on attempt " + i + ":" + ex); if (i == MAX_ATTEMPTS) { break; } try { Log.d(TAG, "Sleeping for " + backoff + " ms before retry"); Thread.sleep(backoff); } catch (InterruptedException e1) { // Activity finished before we complete - exit. Log.d(TAG, "Thread interrupted: abort remaining retries!"); Thread.currentThread().interrupt(); } // increase backoff exponentially backoff *= 2; } } return Boolean.FALSE; } @Override protected void onPostExecute(Boolean status) { //broadcastStatus(status); } }.execute(null, null, null);There is sample code available on Google Code for working with GCM. We reused some of the code to create an utility class that you can get from here.
11. Server Utility class
Once we obtain the registration ID from GCM we send it to our server so that it can be used while sending messages. Our server will persist the registration ID along with the chat email ID. The server is responsible for sending the message to GCM server. More about this later when we implement the server code.We reuse the ServerUtilities class from Google Code with a little modification which you can get from here. It basically contains utility methods to send HTTP POST request to a server.
/** * Issue a POST request to the server. * * @param endpoint POST address. * @param params request parameters. * * @throws IOException propagated from POST. */ private static void post(String endpoint, Map<String, String> params) throws IOException { URL url; try { url = new URL(endpoint); } catch (MalformedURLException e) { throw new IllegalArgumentException("invalid url: " + endpoint); } StringBuilder bodyBuilder = new StringBuilder(); Iterator<Entry<String, String>> iterator = params.entrySet().iterator(); // constructs the POST body using the parameters while (iterator.hasNext()) { Entry<String, String> param = iterator.next(); bodyBuilder.append(param.getKey()).append('=').append(param.getValue()); if (iterator.hasNext()) { bodyBuilder.append('&'); } } String body = bodyBuilder.toString(); //Log.v(TAG, "Posting '" + body + "' to " + url); byte[] bytes = body.getBytes(); HttpURLConnection conn = null; try { conn = (HttpURLConnection) url.openConnection(); conn.setDoOutput(true); conn.setUseCaches(false); conn.setFixedLengthStreamingMode(bytes.length); conn.setRequestMethod("POST"); conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded;charset=UTF-8"); // post the request OutputStream out = conn.getOutputStream(); out.write(bytes); out.close(); // handle the response int status = conn.getResponseCode(); if (status != 200) { throw new IOException("Post failed with error code " + status); } } finally { if (conn != null) { conn.disconnect(); } } }
12. The GCM BroadcastReceiver
GCM delivers messages as a broadcast. Recall that we already registered the reciever in the manifest with appropriate permission and intent filter. Let's implement the class now.public class GcmBroadcastReceiver extends BroadcastReceiver { private static final String TAG = "GcmBroadcastReceiver"; private Context ctx; @Override public void onReceive(Context context, Intent intent) { ctx = context; PowerManager mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE); WakeLock mWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG); mWakeLock.acquire(); try { GoogleCloudMessaging gcm = GoogleCloudMessaging.getInstance(context); String messageType = gcm.getMessageType(intent); if (GoogleCloudMessaging.MESSAGE_TYPE_SEND_ERROR.equals(messageType)) { sendNotification("Send error", false); } else if (GoogleCloudMessaging.MESSAGE_TYPE_DELETED.equals(messageType)) { sendNotification("Deleted messages on server", false); } else { String msg = intent.getStringExtra(DataProvider.COL_MSG); String email = intent.getStringExtra(DataProvider.COL_FROM); ContentValues values = new ContentValues(2); values.put(DataProvider.COL_MSG, msg); values.put(DataProvider.COL_FROM, email); context.getContentResolver().insert(DataProvider.CONTENT_URI_MESSAGES, values); if (Common.isNotify()) { sendNotification("New message", true); } } setResultCode(Activity.RESULT_OK); } finally { mWakeLock.release(); } }Since a broadcast might wake up the device so first get hold of the wake lock. Then insert the message into the database and create a notification to alert the user. Finally, release the wake lock.
You can optionally implement an IntentService to handle the intent and perform background tasks.
private void sendNotification(String text, boolean launchApp) { NotificationManager mNotificationManager = (NotificationManager) ctx.getSystemService(Context.NOTIFICATION_SERVICE); Notification.Builder mBuilder = new Notification.Builder(ctx) .setAutoCancel(true) .setSmallIcon(R.drawable.ic_launcher) .setContentTitle(ctx.getString(R.string.app_name)) .setContentText(text); if (!TextUtils.isEmpty(Common.getRingtone())) { mBuilder.setSound(Uri.parse(Common.getRingtone())); } if (launchApp) { Intent intent = new Intent(ctx, MainActivity.class); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP); PendingIntent pi = PendingIntent.getActivity(ctx, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); mBuilder.setContentIntent(pi); } mNotificationManager.notify(1, mBuilder.getNotification()); }We set a intent to the notification so that user can launch the app directly by clicking the notification.
Next, we'll develop the user interface of the app.