OSDN Git Service

Initial commit of GCM lesson for Cloudsync class.
authorAlexander Lucas <alexlucas@google.com>
Thu, 21 Jun 2012 00:19:42 +0000 (17:19 -0700)
committerAlexander Lucas <alexlucas@google.com>
Wed, 25 Jul 2012 00:32:08 +0000 (17:32 -0700)
Change-Id: I7d4020e60f30a162f74bd2e1d0360c14ee9766b2

docs/html/training/cloudsync/aesync.jd [deleted file]
docs/html/training/cloudsync/backupapi.jd
docs/html/training/cloudsync/gcm.jd [new file with mode: 0644]
docs/html/training/cloudsync/index.jd
docs/html/training/training_toc.cs

diff --git a/docs/html/training/cloudsync/aesync.jd b/docs/html/training/cloudsync/aesync.jd
deleted file mode 100644 (file)
index a21b429..0000000
+++ /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
-
-<div id="tb-wrapper">
-<div id="tb">
-
-<!-- table of contents -->
-<h2>This lesson teaches you how to</h2>
-<ol>
-  <li><a href="#prepare">Prepare Your Environment</a></li>
-  <li><a href="#project">Create Your Project</a></li>
-  <li><a href="#data">Create the Data Layer</a></li>
-  <li><a href="#persistence">Create the Persistence Layer</a></li>
-  <li><a href="#androidapp">Query and Update from the Android App</a></li>
-  <li><a href="#serverc2dm">Configure the C2DM Server-Side</a></li>
-  <li><a href="#clientc2dm">Configure the C2DM Client-Side</a></li>
-</ol>
-<h2>You should also read</h2>
-    <ul>
-      <li><a
-        href="http://developers.google.com/appengine/">App Engine</a></li>
-      <li><a href="http://code.google.com/android/c2dm/">Android Cloud to Device
-        Messaging Framework</a></li>
-    </ul>
-<h2>Try it out</h2>
-
-<p>This lesson uses the Cloud Tasks sample code, originally shown at the
-<a href="http://www.youtube.com/watch?v=M7SxNNC429U">Android + AppEngine: A Developer's Dream Combination</a>
-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.</p>
-<p>
-  <a href="http://code.google.com/p/cloud-tasks-io/" class="button">Cloud Tasks
-  App</a>
-</p>
-
-</div>
-</div>
-
-<p>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.</p>
-
-<p>Following this lesson shows you how to:</p>
-<ul>
-  <li>Build Android and Appengine apps that can communicate with each other</li>
-  <li>Take advantage of Cloud to Device Messaging (C2DM) so your Android app doesn't have to poll for updates</li>
-</ul>
-
-<p>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.</p>
-
-<h2 id="prepare">Prepare Your Environment</h2>
-<p>If you want to follow along with the code example in this lesson, you must do
-the following to prepare your development environment:</p>
-<ul>
-<li>Install the <a href="http://code.google.com/eclipse/">Google Plugin for
-  Eclipse.</a></li>
-<li>Install the <a
-  href="http://code.google.com/webtoolkit/download.html">GWT SDK</a> and the <a
-  href="http://code.google.com/appengine/">Java App Engine SDK</a>. The <a
-  href="http://code.google.com/eclipse/docs/getting_started.html">Quick Start
-  Guide</a> shows you how to install these components.</li>
-<li>Sign up for <a href="http://code.google.com/android/c2dm/signup.html">C2DM
-  access</a>.  We strongly recommend <a
-  href="https://accounts.google.com/SignUp">creating a new Google account</a> specifically for
-connecting to C2DM.  The server component in this lesson uses this <em>role
-  account</em> repeatedly to authenticate with Google servers.
-</li>
-</ul>
-
-<h2 id="project">Create Your Projects</h2>
-<p>After installing the Google Plugin for Eclipse, notice that a new kind of Android project
-exists when you create a new Eclipse project:  The <strong>App Engine Connected
-  Android Project</strong> (under the <strong>Google</strong> 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.</p>
-
-<p class="note"><strong>Note:</strong> Remember to enter the credentials for
-your <i>role account</i> (the one you created to access C2DM services), not an
-account you'd log into as a user, or as an admin.</p>
-
-<p>Once you're done, you'll see two projects waiting for you in your
-workspace&mdash;An Android application and an App Engine application.  Hooray!
-These two applications are already fully functional&mdash; 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:</p>
-
-<p>To spin up the Android application, make sure you have an AVD with a platform
-version of <em>at least</em> Android 2.2 (API Level 8).  Right click on the Android project in
-Eclipse, and go to <strong>Debug As &gt; Local App Engine Connected Android
-  Application</strong>.  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.</p>
-
-<h2 id="data">Create the Data Layer</h2>
-
-<p>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.</p>
-
-<p>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 <strong>(yourApp)-AppEngine
-  &gt; src &gt; (yourapp) &gt; server</strong> package.  Create a new class in there containing some data you want to
-store server-side. The code ends up looking something like this:</p>
-<pre>
-package com.cloudtasks.server;
-
-import javax.persistence.*;
-
-&#64;Entity
-public class Task {
-
-    private String emailAddress;
-    private String name;
-    private String userId;
-    private String note;
-
-    &#64;Id
-    &#64;GeneratedValue(strategy = GenerationType.IDENTITY)
-    private Long id;
-
-    public Task() {
-    }
-
-    public String getEmailAddress() {
-        return this.emailAddress;
-    }
-
-    public Long getId() {
-        return this.id;
-    }
-    ...
-}
-</pre>
-<p>Note the use of annotations:  <code>Entity</code>, <code>Id</code> and
-<code>GeneratedValue</code> are all part of the <a
-  href="http://www.oracle.com/technetwork/articles/javaee/jpa-137156.html">Java
-  Persistence API</a>.  Essentially, the <code>Entity</code> annotation goes
-above the class declaration, and indicates that this class represents an entity
-in your data layer.  The <code>Id</code> and <code>GeneratedValue</code>
-annotations, respectively, indicate the field used as a lookup key for this
-class, and how that id is generated (in this case,
-<code>GenerationType.IDENTITY</code> indicates that the is generated by
-the database).  You can find more on this topic in the App Engine documentation,
-on the page <a
-  href="http://code.google.com/appengine/docs/java/datastore/jpa/overview.html">Using
-  JPA with App Engine</a>.</p>
-
-<p>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 <strong>New &gt; Other</strong> and then, in the resulting
-screen, select <strong>Google &gt; RPC Service.</strong>  A wizard appears, pre-populated
-with all the Entities you created in the previous step,
-which it found by seeking out the <code>&#64;Entity</code> annotation in the
-source files you added.  Pretty neat, right?  Click <strong>Finish</strong>, and the wizard
-creates a Service class with stub methods for the Create, Retrieve, Update and
-Delete (CRUD) operations of all your entities.</p>
-
-<h2 id="persistence">Create the Persistence Layer</h2>
-
-<p>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 <a
-  href="http://code.google.com/apis/storage/">Google Storage for Developers</a>
-and App Engine's built-in <a
-  href="http://code.google.com/appengine/docs/java/gettingstarted/usingdatastore.html">Datastore</a>.
-The sample code for this lesson uses DataStore code.</p>
-
-<p>Create a class in your <code>com.cloudtasks.server</code> package to handle
-persistence layer input and output.  In order to access the data store, use the <a
-  href="http://db.apache.org/jdo/api20/apidocs/javax/jdo/PersistenceManager.html">PersistenceManager</a>
-class.  You can generate an instance of this class using the PMF class in the
-<code>com.google.android.c2dm.server.PMF</code> package, and then use that to
-perform basic CRUD operations on your data store, like this:</p>
-<pre>
-/**
-* 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();
-    }
-}
-</pre>
-
-<p>You can also use <a
-  href="http://code.google.com/appengine/docs/python/datastore/queryclass.html">Query</a>
-objects to retrieve data from your Datastore.  Here's an example of a method
-that searches out an object by its ID.</p>
-
-<pre>
-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() + " &amp;&amp; emailAddress=='" + getUserEmail() + "'");
-        List&lt;Task> list = (List&lt;Task>) query.execute();
-        return list.size() == 0 ? null : list.get(0);
-    } catch (RuntimeException e) {
-        System.out.println(e);
-        throw e;
-    } finally {
-        pm.close();
-    }
-}
-</pre>
-
-<p>For a good example of a class that encapsulates the persistence layer for
-you, check out the <a
-  href="http://code.google.com/p/cloud-tasks-io/source/browse/trunk/CloudTasks-AppEngine/src/com/cloudtasks/server/DataStore.java">DataStore</a>
-class in the Cloud Tasks app.</p>
-
-
-
-<h2 id="androidapp">Query and Update from the Android App</h2>
-
-<p>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.</p>
-
-<p>Pop open the source code for the main Activity in your project and look for
-<code>&lt;YourProjectName&gt; Activity.java</code>, then for the method
-<code>setHelloWorldScreenContent()</code>.  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 <em>not</em> 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 <a
-  href="http://code.google.com/webtoolkit/doc/latest/DevGuideRequestFactory.html">RequestFactory</a>
-class generated that was auto-generated for you by the Eclipse plugin (called
-MyRequestFactory in the example below), and various {@code Request} types.</p>
-
-<p>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:</p>
-
-<pre>
-public void fetchTasks (Long id) {
-  // Request is wrapped in an AsyncTask to avoid making a network request
-  // on the UI thread.
-    new AsyncTask&lt;Long, Void, List&lt;TaskProxy>>() {
-        &#64;Override
-        protected List&lt;TaskProxy> doInBackground(Long... arguments) {
-            final List&lt;TaskProxy> list = new ArrayList&lt;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&lt;List&lt;TaskProxy>>() {
-                    &#64;Override
-                    public void onSuccess(List&lt;TaskProxy> arg0) {
-                      list.addAll(arg0);
-                    }
-                });
-            } else {
-                newTask = true;
-                factory.taskRequest().readTask(arguments[0]).fire(new Receiver&lt;TaskProxy>() {
-                    &#64;Override
-                    public void onSuccess(TaskProxy arg0) {
-                      list.add(arg0);
-                    }
-                });
-            }
-        return list;
-    }
-
-    &#64;Override
-    protected void onPostExecute(List&lt;TaskProxy> result) {
-        TaskNinjaActivity.this.dump(result);
-    }
-
-    }.execute(id);
-}
-...
-
-public void dump (List&lt;TaskProxy> tasks) {
-    for (TaskProxy task : tasks) {
-        Log.i("Task output", task.getName() + "\n" + task.getNote());
-    }
-}
-</pre>
-
-<p>This {@link android.os.AsyncTask} returns a list of
-<code>TaskProxy</code> 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 <code>TaskProxy</code> class.</p>
-
-<p>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
-<code>AsyncTask</code> to avoid doing networking on the UI thread.  The end
-result looks something like this.</p>
-
-<pre>
-new AsyncTask&lt;Void, Void, Void>() {
-    &#64;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();
-</pre>
-
-<h2 id="serverc2dm">Configure the C2DM Server-Side</h2>
-
-<p>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:
-</p>
-
-<pre>
-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);
-}
-</pre>
-
-<p>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:</p>
-
-<pre>
-public class TaskChange {
-    public static String UPDATE = "Update";
-    public static String DELETE = "Delete";
-    public static String SEPARATOR = ":";
-}
-</pre>
-
-<h2 id="clientc2dm">Configure the C2DM Client-Side</h2>
-
-<p>In order to define the Android applications behavior when a C2DM is recieved,
-open up the <code>C2DMReceiver</code> class, and browse to the
-<code>onMessage()</code> method.  Tweak this method to update based on the content
-of the message.</p>
-<pre>
-//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]));
-        }
-    }
-}
-</pre>
-
-<pre>
-// 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);
-    }
-}
-</pre>
-<p>
-Once you have C2DM set up to trigger local updates, you're all done.
-Congratulations, you have a cloud-connected Android application!</p>
index 3055596..a5436c6 100644 (file)
@@ -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 (file)
index 0000000..dcc1b6b
--- /dev/null
@@ -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
+
+<div id="tb-wrapper">
+  <div id="tb">
+    <h2>This lesson teaches you to</h2>
+    <ol>
+      <li><a href="#multicast">Send Multicast Messages Efficiently</a></li>
+      <li><a href="#collapse">Collapse Messages that can Be Replaced</a></li>
+      <li><a href="#embed">Embed Data Directly in the GCM Message</a></li>
+      <li><a href="#react">React Intelligently to GCM Messages</a></li>
+    </ol>
+    <h2>You should also read</h2>
+    <ul>
+      <li><a href="http://developer.android.com/guide/google/gcm/index.html">Google
+      Cloud Messaging for Android</a></li>
+    </ul>
+  </div>
+</div>
+
+<p>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.</p>
+
+<p>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 <a
+  href="http://developer.google.com/android/gcm/demo">GCM
+  Tutorial</a>.</p>
+
+<h2 id="multicast">Send Multicast Messages Efficiently</h2>
+<p>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.</p>
+
+<p>Taking advantage of this functionality is easy.  If you're using the <a
+  href="http://developer.android.com/guide/google/gcm/gs.html#libs">GCM helper
+  library</a> for Java, simply provide a <code>List<String></code> collection of
+registration IDs to the <code>send</code> or <code>sendNoRetry</code> method,
+instead of a single registration ID.</p>
+
+<pre>
+// This method name is completely fabricated, but you get the idea.
+List<String> 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);
+</pre>
+
+<p>For those implementing GCM support in a language other than Java, construct
+an HTTP POST request with the following headers:</p>
+<ul>
+  <li><code>Authorization: key=YOUR_API_KEY</code></li>
+  <li><code>Content-type: application/json</code></li>
+</ul>
+
+<p>Then encode the parameters you want into a JSON object, listing all the
+registration IDs under the key <code>registration_ids</code>.  The snippet below
+serves as an example.  All parameters except <code>registration_ids</code> are
+optional, and the items nested in <code>data</code> represent the user-defined payload, not
+GCM-defined parameters.  The endpoint for this HTTP POST message will be
+<code>https://android.googleapis.com/gcm/send</code>.</p>
+
+<pre>
+{ "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"]
+}
+</pre>
+
+<p>For a more thorough overview of the format of multicast GCM messages, see the <a
+  href="http://developer.android.com/guide/google/gcm/gcm.html#send-msg">Sending
+  Messages</a> section of the GCM guide.</pre>
+
+<h2 id="collapse">Collapse Messages that Can Be Replaced</h2>
+<p>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.</p>
+
+<p>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.</p>
+
+<p>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 <code>collapseKey(String key)</code> method.</p>
+
+<pre>
+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();
+</pre>
+
+<p>If not using the helper library, simply add a variable to the
+POST header you're constructing, with <code>collapse_key</code> as the field
+name, and the string you're using for that set of updates as the value.</p>
+
+
+
+<h2 id="embed">Embed Data Directly in the GCM Message</h2>
+<p>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:
+<ul>
+  <li>The total data fits inside the 4kb limit.</li>
+  <li>Each message is important, and should be preserved.</li>
+  <li>It doesn't make sense to collapse multiple GCM messages into a single
+  "new data on the server" tickle.</li>
+</ul>
+
+<p>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.</p>
+
+<p>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.</p>
+<p>This strategy isn't appropriate for sending large amounts of data, for a few
+reasons:</p>
+<ul>
+  <li>Rate limits are in place to prevent malicious or poorly coded apps from spamming an
+  individual device with messages.</li>
+  <li>Messages aren't guaranteed to arrive in-order.</li>
+  <li>Messages aren't guaranteed to arrive as fast as you send them out.  Even
+  if the device receives one GCM message a second, at a max of 1K, that's 8kbps, or
+  about the speed of home dial-up internet in the early 1990's.  Your app rating
+  on Google Play will reflect having done that to your users.</p>
+</ul>
+
+<p>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.</p>
+
+<h2 id="react">React Intelligently to GCM Messages</h2>
+<p>Your application should not only react to incoming GCM messages, but react
+<em>intelligently</em>.  How to react depends on the context.</p>
+
+<h3>Don't be irritating</h3>
+<p>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,
+<a
+  href="http://developer.android.com/guide/topics/ui/notifiers/notifications.html#Updating">update
+  your existing notification</a> 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.</p>
+
+<h3>Sync smarter, not harder</h3>
+<p>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:</p>
+<ul>
+  <li><code>refresh</code> &mdash; Your app basically got told to request a dump of
+  every feed it follows.  Your app would either need to send feed requests to 100 different servers, or
+  if you have an aggregator on your server, send a request to retrieve, bundle
+  and
+  transmit recent data from 100 different feeds, every time one updates.</li>
+  <li><code>refresh</code>, <code>feedID</code> &mdash; Better:  Your app knows to check
+  a specific feed for updates.</li>
+  <li><code>refresh</code>, <code>feedID</code>, <code>timestamp</code> &mdash;
+  Best:  If the user happened to manually refresh before the GCM message
+  arrived, the application can compare timestamps of the most recent post, and
+  determine that it <em>doesn't need to do anything</em>.
+</ul>
index e53844b..91885e8 100644 (file)
@@ -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.
 <h2>Lessons</h2>
 
 <dl>
-  <dt><strong><a href="aesync.html">Syncing with App Engine.</a></strong></dt>
-  <dd>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.</dd>
-  <dt><strong><a href="backupapi.html">Using the Backup
-      API</a></strong></dt>
+  <dt><strong><a href="backupapi.html">Using the Backup API</a></strong></dt>
   <dd>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</dd>
+  <dt><strong><a href="gcm.html">Making the Most of Google Cloud Messaging</a></strong></dt>
+  <dd>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.</dd>
 </dl>
 
index 77a6837..37b69d4 100644 (file)
             <span class="en">Syncing to the Cloud</span>
           </a></div>
         <ul>
-          <li><a href="<?cs var:toroot ?>training/cloudsync/aesync.html">
-            <span class="en">Syncing with App Engine</span>
-          </a>
-          </li>
           <li><a href="<?cs var:toroot ?>training/cloudsync/backupapi.html">
             <span class="en">Using the Backup API</span>
           </a>
           </li>
+          <li><a href="<?cs var:toroot ?>training/cloudsync/gcm.html">
+            <span class="en">Making the Most of Google Cloud Messaging</span>
+          </a>
+          </li>
         </ul>
       </li>