From e38a6c50ea6f6c1238276154ca0631842b6ab06c Mon Sep 17 00:00:00 2001 From: Alexander Lucas Date: Wed, 20 Jun 2012 17:19:42 -0700 Subject: Initial commit of GCM lesson for Cloudsync class. Change-Id: I7d4020e60f30a162f74bd2e1d0360c14ee9766b2 --- docs/html/training/cloudsync/aesync.jd | 432 ------------------------------ docs/html/training/cloudsync/backupapi.jd | 5 +- docs/html/training/cloudsync/gcm.jd | 218 +++++++++++++++ docs/html/training/cloudsync/index.jd | 15 +- docs/html/training/training_toc.cs | 8 +- 5 files changed, 232 insertions(+), 446 deletions(-) delete mode 100644 docs/html/training/cloudsync/aesync.jd create mode 100644 docs/html/training/cloudsync/gcm.jd diff --git a/docs/html/training/cloudsync/aesync.jd b/docs/html/training/cloudsync/aesync.jd deleted file mode 100644 index a21b4293ebaa..000000000000 --- a/docs/html/training/cloudsync/aesync.jd +++ /dev/null @@ -1,432 +0,0 @@ -page.title=Syncing with App Engine -parent.title=Syncing to the Cloud -parent.link=index.html - -trainingnavtop=true -next.title=Using the Backup API -next.link=backupapi.html - -@jd:body - -
-
- - -

This lesson teaches you how to

-
    -
  1. Prepare Your Environment
  2. -
  3. Create Your Project
  4. -
  5. Create the Data Layer
  6. -
  7. Create the Persistence Layer
  8. -
  9. Query and Update from the Android App
  10. -
  11. Configure the C2DM Server-Side
  12. -
  13. Configure the C2DM Client-Side
  14. -
-

You should also read

- -

Try it out

- -

This lesson uses the Cloud Tasks sample code, originally shown at the -Android + AppEngine: A Developer's Dream Combination -talk at Google I/O. You can use the sample application as a source of reusable code for your own -application, or simply as a reference for how the Android and cloud pieces of the overall -application fit together. You can also build the sample application and see how it runs -on your own device or emulator.

-

- Cloud Tasks - App -

- -
-
- -

Writing an app that syncs to the cloud can be a challenge. There are many little -details to get right, like server-side auth, client-side auth, a shared data -model, and an API. One way to make this much easier is to use the Google Plugin -for Eclipse, which handles a lot of the plumbing for you when building Android -and App Engine applications that talk to each other. This lesson walks you through building such a project.

- -

Following this lesson shows you how to:

- - -

This lesson focuses on local development, and does not cover distribution -(i.e, pushing your App Engine app live, or publishing your Android App to -market), as those topics are covered extensively elsewhere.

- -

Prepare Your Environment

-

If you want to follow along with the code example in this lesson, you must do -the following to prepare your development environment:

- - -

Create Your Projects

-

After installing the Google Plugin for Eclipse, notice that a new kind of Android project -exists when you create a new Eclipse project: The App Engine Connected - Android Project (under the Google project category). -A wizard guides you through creating this project, -during the course of which you are prompted to enter the account credentials for the role -account you created.

- -

Note: Remember to enter the credentials for -your role account (the one you created to access C2DM services), not an -account you'd log into as a user, or as an admin.

- -

Once you're done, you'll see two projects waiting for you in your -workspace—An Android application and an App Engine application. Hooray! -These two applications are already fully functional— the wizard has -created a sample application which lets you authenticate to the App Engine -application from your Android device using AccountManager (no need to type in -your credentials), and an App Engine app that can send messages to any logged-in -device using C2DM. In order to spin up your application and take it for a test -drive, do the following:

- -

To spin up the Android application, make sure you have an AVD with a platform -version of at least Android 2.2 (API Level 8). Right click on the Android project in -Eclipse, and go to Debug As > Local App Engine Connected Android - Application. This launches the emulator in such a way that it can -test C2DM functionality (which typically works through Google Play). It'll -also launch a local instance of App Engine containing your awesome -application.

- -

Create the Data Layer

- -

At this point you have a fully functional sample application running. Now -it's time to start changing the code to create your own application.

- -

First, create the data model that defines the data shared between -the App Engine and Android applications. To start, open up the source folder of -your App Engine project, and navigate down to the (yourApp)-AppEngine - > src > (yourapp) > server package. Create a new class in there containing some data you want to -store server-side. The code ends up looking something like this:

-
-package com.cloudtasks.server;
-
-import javax.persistence.*;
-
-@Entity
-public class Task {
-
-    private String emailAddress;
-    private String name;
-    private String userId;
-    private String note;
-
-    @Id
-    @GeneratedValue(strategy = GenerationType.IDENTITY)
-    private Long id;
-
-    public Task() {
-    }
-
-    public String getEmailAddress() {
-        return this.emailAddress;
-    }
-
-    public Long getId() {
-        return this.id;
-    }
-    ...
-}
-
-

Note the use of annotations: Entity, Id and -GeneratedValue are all part of the Java - Persistence API. Essentially, the Entity annotation goes -above the class declaration, and indicates that this class represents an entity -in your data layer. The Id and GeneratedValue -annotations, respectively, indicate the field used as a lookup key for this -class, and how that id is generated (in this case, -GenerationType.IDENTITY indicates that the is generated by -the database). You can find more on this topic in the App Engine documentation, -on the page Using - JPA with App Engine.

- -

Once you've written all the classes that represent entities in your data -layer, you need a way for the Android and App Engine applications to communicate -about this data. This communication is enabled by creating a Remote Procedure -Call (RPC) service. -Typically, this involves a lot of monotonous code. Fortunately, there's an easy way! Right -click on the server project in your App Engine source folder, and in the context -menu, navigate to New > Other and then, in the resulting -screen, select Google > RPC Service. A wizard appears, pre-populated -with all the Entities you created in the previous step, -which it found by seeking out the @Entity annotation in the -source files you added. Pretty neat, right? Click Finish, and the wizard -creates a Service class with stub methods for the Create, Retrieve, Update and -Delete (CRUD) operations of all your entities.

- -

Create the Persistence Layer

- -

The persistence layer is where your application data is stored -long-term, so any information you want to keep for your users needs to go here. -You have several options for writing your persistence layer, depending on -what kind of data you want to store. A few of the options hosted by Google -(though you don't have to use these services) include Google Storage for Developers -and App Engine's built-in Datastore. -The sample code for this lesson uses DataStore code.

- -

Create a class in your com.cloudtasks.server package to handle -persistence layer input and output. In order to access the data store, use the PersistenceManager -class. You can generate an instance of this class using the PMF class in the -com.google.android.c2dm.server.PMF package, and then use that to -perform basic CRUD operations on your data store, like this:

-
-/**
-* Remove this object from the data store.
-*/
-public void delete(Long id) {
-    PersistenceManager pm = PMF.get().getPersistenceManager();
-    try {
-        Task item = pm.getObjectById(Task.class, id);
-        pm.deletePersistent(item);
-    } finally {
-        pm.close();
-    }
-}
-
- -

You can also use Query -objects to retrieve data from your Datastore. Here's an example of a method -that searches out an object by its ID.

- -
-public Task find(Long id) {
-    if (id == null) {
-        return null;
-    }
-
-    PersistenceManager pm = PMF.get().getPersistenceManager();
-    try {
-        Query query = pm.newQuery("select from " + Task.class.getName()
-        + " where id==" + id.toString() + " && emailAddress=='" + getUserEmail() + "'");
-        List<Task> list = (List<Task>) query.execute();
-        return list.size() == 0 ? null : list.get(0);
-    } catch (RuntimeException e) {
-        System.out.println(e);
-        throw e;
-    } finally {
-        pm.close();
-    }
-}
-
- -

For a good example of a class that encapsulates the persistence layer for -you, check out the DataStore -class in the Cloud Tasks app.

- - - -

Query and Update from the Android App

- -

In order to keep in sync with the App Engine application, your Android application -needs to know how to do two things: Pull data from the cloud, and send data up -to the cloud. Much of the plumbing for this is generated by the -plugin, but you need to wire it up to your Android user interface yourself.

- -

Pop open the source code for the main Activity in your project and look for -<YourProjectName> Activity.java, then for the method -setHelloWorldScreenContent(). Obviously you're not building a -HelloWorld app, so delete this method entirely and replace it -with something relevant. However, the boilerplate code has some very important -characteristics. For one, the code that communicates with the cloud is wrapped -in an {@link android.os.AsyncTask} and therefore not hitting the -network on the UI thread. Also, it gives an easy template for how to access -the cloud in your own code, using the RequestFactory -class generated that was auto-generated for you by the Eclipse plugin (called -MyRequestFactory in the example below), and various {@code Request} types.

- -

For instance, if your server-side data model included an object called {@code -Task} when you generated an RPC layer it automatically created a -{@code TaskRequest} class for you, as well as a {@code TaskProxy} representing the individual -task. In code, requesting a list of all these tasks from the server looks -like this:

- -
-public void fetchTasks (Long id) {
-  // Request is wrapped in an AsyncTask to avoid making a network request
-  // on the UI thread.
-    new AsyncTask<Long, Void, List<TaskProxy>>() {
-        @Override
-        protected List<TaskProxy> doInBackground(Long... arguments) {
-            final List<TaskProxy> list = new ArrayList<TaskProxy>();
-            MyRequestFactory factory = Util.getRequestFactory(mContext,
-            MyRequestFactory.class);
-            TaskRequest taskRequest = factory.taskNinjaRequest();
-
-            if (arguments.length == 0 || arguments[0] == -1) {
-                factory.taskRequest().queryTasks().fire(new Receiver<List<TaskProxy>>() {
-                    @Override
-                    public void onSuccess(List<TaskProxy> arg0) {
-                      list.addAll(arg0);
-                    }
-                });
-            } else {
-                newTask = true;
-                factory.taskRequest().readTask(arguments[0]).fire(new Receiver<TaskProxy>() {
-                    @Override
-                    public void onSuccess(TaskProxy arg0) {
-                      list.add(arg0);
-                    }
-                });
-            }
-        return list;
-    }
-
-    @Override
-    protected void onPostExecute(List<TaskProxy> result) {
-        TaskNinjaActivity.this.dump(result);
-    }
-
-    }.execute(id);
-}
-...
-
-public void dump (List<TaskProxy> tasks) {
-    for (TaskProxy task : tasks) {
-        Log.i("Task output", task.getName() + "\n" + task.getNote());
-    }
-}
-
- -

This {@link android.os.AsyncTask} returns a list of -TaskProxy objects, and sends it to the debug {@code dump()} method -upon completion. Note that if the argument list is empty, or the first argument -is a -1, all tasks are retrieved from the server. Otherwise, only the ones with -IDs in the supplied list are returned. All the fields you added to the task -entity when building out the App Engine application are available via get/set -methods in the TaskProxy class.

- -

In order to create new tasks and send them to the cloud, create a request -object and use it to create a proxy object. Then populate the proxy object and -call its update method. Once again, this should be done in an -AsyncTask to avoid doing networking on the UI thread. The end -result looks something like this.

- -
-new AsyncTask<Void, Void, Void>() {
-    @Override
-    protected Void doInBackground(Void... arg0) {
-        MyRequestFactory factory = (MyRequestFactory)
-                Util.getRequestFactory(TasksActivity.this,
-                MyRequestFactory.class);
-        TaskRequest request = factory.taskRequest();
-
-        // Create your local proxy object, populate it
-        TaskProxy task = request.create(TaskProxy.class);
-        task.setName(taskName);
-        task.setNote(taskDetails);
-        task.setDueDate(dueDate);
-
-        // To the cloud!
-        request.updateTask(task).fire();
-        return null;
-    }
-}.execute();
-
- -

Configure the C2DM Server-Side

- -

In order to set up C2DM messages to be sent to your Android device, go back -into your App Engine codebase, and open up the service class that was created -when you generated your RPC layer. If the name of your project is Foo, -this class is called FooService. Add a line to each of the methods for -adding, deleting, or updating data so that a C2DM message is sent to the -user's device. Here's an example of an update task: -

- -
-public static Task updateTask(Task task) {
-    task.setEmailAddress(DataStore.getUserEmail());
-    task = db.update(task);
-    DataStore.sendC2DMUpdate(TaskChange.UPDATE + TaskChange.SEPARATOR + task.getId());
-    return task;
-}
-
-// Helper method.  Given a String, send it to the current user's device via C2DM.
-public static void sendC2DMUpdate(String message) {
-    UserService userService = UserServiceFactory.getUserService();
-    User user = userService.getCurrentUser();
-    ServletContext context = RequestFactoryServlet.getThreadLocalRequest().getSession().getServletContext();
-    SendMessage.sendMessage(context, user.getEmail(), message);
-}
-
- -

In the following example, a helper class, {@code TaskChange}, has been created with a few -constants. Creating such a helper class makes managing the communication -between App Engine and Android apps much easier. Just create it in the shared -folder, define a few constants (flags for what kind of message you're sending -and a seperator is typically enough), and you're done. By way of example, -the above code works off of a {@code TaskChange} class defined as this:

- -
-public class TaskChange {
-    public static String UPDATE = "Update";
-    public static String DELETE = "Delete";
-    public static String SEPARATOR = ":";
-}
-
- -

Configure the C2DM Client-Side

- -

In order to define the Android applications behavior when a C2DM is recieved, -open up the C2DMReceiver class, and browse to the -onMessage() method. Tweak this method to update based on the content -of the message.

-
-//In your C2DMReceiver class
-
-public void notifyListener(Intent intent) {
-    if (listener != null) {
-        Bundle extras = intent.getExtras();
-        if (extras != null) {
-            String message = (String) extras.get("message");
-            String[] messages = message.split(Pattern.quote(TaskChange.SEPARATOR));
-            listener.onTaskUpdated(messages[0], Long.parseLong(messages[1]));
-        }
-    }
-}
-
- -
-// Elsewhere in your code, wherever it makes sense to perform local updates
-public void onTasksUpdated(String messageType, Long id) {
-    if (messageType.equals(TaskChange.DELETE)) {
-        // Delete this task from your local data store
-        ...
-    } else {
-        // Call that monstrous Asynctask defined earlier.
-        fetchTasks(id);
-    }
-}
-
-

-Once you have C2DM set up to trigger local updates, you're all done. -Congratulations, you have a cloud-connected Android application!

diff --git a/docs/html/training/cloudsync/backupapi.jd b/docs/html/training/cloudsync/backupapi.jd index 30555967be38..a5436c6d2eec 100644 --- a/docs/html/training/cloudsync/backupapi.jd +++ b/docs/html/training/cloudsync/backupapi.jd @@ -3,8 +3,9 @@ parent.title=Syncing to the Cloud parent.link=index.html trainingnavtop=true -previous.title=Syncing with App Engine -previous.link=aesync.html + +next.title=Making the Most of Google Cloud Messaging +next.link=gcm.html @jd:body diff --git a/docs/html/training/cloudsync/gcm.jd b/docs/html/training/cloudsync/gcm.jd new file mode 100644 index 000000000000..dcc1b6b39850 --- /dev/null +++ b/docs/html/training/cloudsync/gcm.jd @@ -0,0 +1,218 @@ +page.title=Making the Most of Google Cloud Messaging +parent.title=Syncing to the Cloud +parent.link=index.html + +trainingnavtop=true + +previous.title=Using the Backup API +previous.link=backupapi.html + +@jd:body + +
+ +
+ +

Google Cloud Messaging (GCM) is a free service for sending +messages to Android devices. GCM messaging can greatly enhance the user +experience. Your application can stay up to date without wasting battery power +on waking up the radio and polling the server when there are no updates. Also, +GCM allows you to attach up to 1,000 recipients to a single message, letting you easily contact +large user bases quickly when appropriate, while minimizing the work load on +your server.

+ +

This lesson covers some of the best practices +for integrating GCM into your application, and assumes you are already familiar +with basic implementation of this service. If this is not the case, you can read the GCM + Tutorial.

+ +

Send Multicast Messages Efficiently

+

One of the most useful features in GCM is support for up to 1,000 recipients for +a single message. This capability makes it much easier to send out important messages to +your entire user base. For instance, let's say you had a message that needed to +be sent to 1,000,000 of your users, and your server could handle sending out +about 500 messages per second. If you send each message with only a single +recipient, it would take 1,000,000/500 = 2,000 seconds, or around half an hour. +However, attaching 1,000 recipients to each message, the total time required to +send a message out to 1,000,000 recipients becomes (1,000,000/1,000) / 500 = 2 +seconds. This is not only useful, but important for timely data, such as natural +disaster alerts or sports scores, where a 30 minute interval might render the +information useless.

+ +

Taking advantage of this functionality is easy. If you're using the GCM helper + library for Java, simply provide a List collection of +registration IDs to the send or sendNoRetry method, +instead of a single registration ID.

+ +
+// This method name is completely fabricated, but you get the idea.
+List regIds = whoShouldISendThisTo(message);
+
+// If you want the SDK to automatically retry a certain number of times, use the
+// standard send method.
+MulticastResult result = sender.send(message, regIds, 5);
+
+// Otherwise, use sendNoRetry.
+MulticastResult result = sender.sendNoRetry(message, regIds);
+
+ +

For those implementing GCM support in a language other than Java, construct +an HTTP POST request with the following headers:

+ + +

Then encode the parameters you want into a JSON object, listing all the +registration IDs under the key registration_ids. The snippet below +serves as an example. All parameters except registration_ids are +optional, and the items nested in data represent the user-defined payload, not +GCM-defined parameters. The endpoint for this HTTP POST message will be +https://android.googleapis.com/gcm/send.

+ +
+{ "collapse_key": "score_update",
+   "time_to_live": 108,
+   "delay_while_idle": true,
+   "data": {
+       "score": "4 x 8",
+       "time": "15:16.2342"
+   },
+   "registration_ids":["4", "8", "15", "16", "23", "42"]
+}
+
+ +

For a more thorough overview of the format of multicast GCM messages, see the Sending + Messages section of the GCM guide. + +

Collapse Messages that Can Be Replaced

+

GCM messages are often a tickle, telling the mobile application to +contact the server for fresh data. In GCM, it's possible (and recommended) to +create collapsible messages for this situation, wherein new messages replace +older ones. Let's take the example +of sports scores. If you send out a message to all users following a certain +game with the updated score, and then 15 minutes later an updated score message +goes out, the earlier one no longer matters. For any users who haven't received +the first message yet, there's no reason to send both, and force the device to +react (and possibly alert the user) twice when only one of the messages is still +important.

+ +

When you define a collapse key, when multiple messages are queued up in the GCM +servers for the same user, only the last one with any given collapse key is +delivered. For a situation like with sports scores, this saves the device from +doing needless work and potentially over-notifying the user. For situations +that involve a server sync (like checking email), this can cut down on the +number of syncs the device has to do. For instance, if there are 10 emails +waiting on the server, and ten "new email" GCM tickles have been sent to the +device, it only needs one, since it should only sync once.

+ +

In order to use this feature, just add a collapse key to your outgoing +message. If you're using the GCM helper library, use the Message class's collapseKey(String key) method.

+ +
+Message message = new Message.Builder(regId)
+    .collapseKey("game4_scores") // The key for game 4.
+    .ttl(600) // Time in seconds to keep message queued if device offline.
+    .delayWhileIdle(true) // Wait for device to become active before sending.
+    .addPayload("key1", "value1")
+    .addPayload("key2", "value2")
+    .build();
+
+ +

If not using the helper library, simply add a variable to the +POST header you're constructing, with collapse_key as the field +name, and the string you're using for that set of updates as the value.

+ + + +

Embed Data Directly in the GCM Message

+

Often, GCM messages are meant to be a tickle, or indication to the device +that there's fresh data waiting on a server somewhere. However, a GCM message +can be up to 4kb in size, so sometimes it makes sense to simply send the +data within the GCM message itself, so that the device doesn't need to contact the +server at all. Consider this approach for situations where all of the +following statements are true: +

+ +

For instance, short messages or encoded player moves +in a turn-based network game are examples of good use-cases for data to embed directly +into a GCM message. Email is an example of a bad use-case, since messages are +often larger than 4kb, +and users don't need a GCM message for each email waiting for them on +the server.

+ +

Also consider this approach when sending +multicast messages, so you don't tell every device across your user base to hit +your server for updates simultaneously.

+

This strategy isn't appropriate for sending large amounts of data, for a few +reasons:

+ + +

When used appropriately, directly embedding data in the GCM message can speed +up the perceived speediness of your application, by letting it skip a round trip +to the server.

+ +

React Intelligently to GCM Messages

+

Your application should not only react to incoming GCM messages, but react +intelligently. How to react depends on the context.

+ +

Don't be irritating

+

When it comes to alerting your user of fresh data, it's easy to cross the line +from "useful" to "annoying". If your application uses status bar notifications, +update + your existing notification instead of creating a second one. If you +beep or vibrate to alert the user, consider setting up a timer. Don't let the +application alert more than once a minute, lest users be tempted to uninstall +your application, turn the device off, or toss it in a nearby river.

+ +

Sync smarter, not harder

+

When using GCM as an indicator to the device that data needs to be downloaded +from the server, remember you have 4kb of metadata you can send along to +help your application be smart about it. For instance, if you have a feed +reading app, and your user has 100 feeds that they follow, help the device be +smart about what it downloads from the server! Look at the following examples +of what metadata is sent to your application in the GCM payload, and how the application +can react:

+ diff --git a/docs/html/training/cloudsync/index.jd b/docs/html/training/cloudsync/index.jd index e53844b5015c..91885e85ab56 100644 --- a/docs/html/training/cloudsync/index.jd +++ b/docs/html/training/cloudsync/index.jd @@ -2,8 +2,8 @@ page.title=Syncing to the Cloud trainingnavtop=true startpage=true -next.title=Syncing with App Engine -next.link=aesync.html +next.title=Making the Most of Google Cloud Messaging +next.link=gcm.html @jd:body @@ -21,14 +21,13 @@ installing your application on a new device.

Lessons

-
Syncing with App Engine.
-
Learn how to create a paired App Engine app and Android app which share a - data model, authenticates using the AccountManager, and communicate with each - other via REST and C2DM.
-
Using the Backup - API
+
Using the Backup API
Learn how to integrate the Backup API into your Android Application, so that user data such as preferences, notes, and high scores update seamlessly across all of a user's devices
+
Making the Most of Google Cloud Messaging
+
Learn how to efficiently send multicast messages, react intelligently to + incoming Google Cloud Messaging (GCM) messages, and use GCM messages to + efficiently sync with the server.
diff --git a/docs/html/training/training_toc.cs b/docs/html/training/training_toc.cs index 77a68371a885..37b69d4c8c70 100644 --- a/docs/html/training/training_toc.cs +++ b/docs/html/training/training_toc.cs @@ -203,14 +203,14 @@ Syncing to the Cloud -- cgit v1.2.3-59-g8ed1b