diff --git a/docs/html/training/cloudsync/aesync.jd b/docs/html/training/cloudsync/aesync.jd deleted file mode 100644 index a21b4293ebaa9..0000000000000 --- 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 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.
- - -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.
- -If you want to follow along with the code example in this lesson, you must do -the following to prepare your development environment:
-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.
- -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.
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.
- - - -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();
-
-
-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 = ":";
-}
-
-
-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 30555967be389..a5436c6d2eec6 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 0000000000000..dcc1b6b398507 --- /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.
+ +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:
+Authorization: key=YOUR_API_KEYContent-type: application/jsonThen 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. + +
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.
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.
+ +Your application should not only react to incoming GCM messages, but react +intelligently. How to react depends on the context.
+ +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.
+ +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:
+refresh — 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.refresh, feedID — Better: Your app knows to check
+ a specific feed for updates.refresh, feedID, timestamp —
+ 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 doesn't need to do anything.
+