From 773740ed79075e6daffaea0c62857348ab54d93e Mon Sep 17 00:00:00 2001 From: Alexander Lucas Date: Mon, 16 Apr 2012 13:13:09 -0700 Subject: Initial commit of backup API section of cloudsync class. Branch switch from ics-mr0, original post here: https://android-git.corp.google.com/g/#/c/150873/ Change-Id: I4c53b99bd326c9f8fa485392e87dfba35e662936 --- docs/html/resources/resources_toc.cs | 29 +- docs/html/training/cloudsync/aesync.jd | 432 ++++++++++++++++++++++++++++++ docs/html/training/cloudsync/backupapi.jd | 193 +++++++++++++ docs/html/training/cloudsync/index.jd | 34 +++ 4 files changed, 683 insertions(+), 5 deletions(-) create mode 100644 docs/html/training/cloudsync/aesync.jd create mode 100644 docs/html/training/cloudsync/backupapi.jd create mode 100644 docs/html/training/cloudsync/index.jd diff --git a/docs/html/resources/resources_toc.cs b/docs/html/resources/resources_toc.cs index 686bde356ef8..a21708ce283c 100644 --- a/docs/html/resources/resources_toc.cs +++ b/docs/html/resources/resources_toc.cs @@ -123,6 +123,23 @@ class="new"> new! +
  • +
    + Syncing to the Cloud new! +
    + +
  • + +
  • Adding Search Functionality new! @@ -369,11 +386,11 @@ class="new"> new!
  • Displaying Bitmaps in Your UI -
  • - + - - - + + + + +
  • Technical Resources diff --git a/docs/html/training/cloudsync/aesync.jd b/docs/html/training/cloudsync/aesync.jd new file mode 100644 index 000000000000..c60d28b1bbab --- /dev/null +++ b/docs/html/training/cloudsync/aesync.jd @@ -0,0 +1,432 @@ +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 list = (List) 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>() {
    +        @Override
    +        protected List doInBackground(Long... arguments) {
    +            final List list = new ArrayList();
    +            MyRequestFactory factory = Util.getRequestFactory(mContext,
    +            MyRequestFactory.class);
    +            TaskRequest taskRequest = factory.taskNinjaRequest();
    +
    +            if (arguments.length == 0 || arguments[0] == -1) {
    +                factory.taskRequest().queryTasks().fire(new Receiver>() {
    +                    @Override
    +                    public void onSuccess(List arg0) {
    +                      list.addAll(arg0);
    +                    }
    +                });
    +            } else {
    +                newTask = true;
    +                factory.taskRequest().readTask(arguments[0]).fire(new Receiver() {
    +                    @Override
    +                    public void onSuccess(TaskProxy arg0) {
    +                      list.add(arg0);
    +                    }
    +                });
    +            }
    +        return list;
    +    }
    +
    +    @Override
    +    protected void onPostExecute(List result) {
    +        TaskNinjaActivity.this.dump(result);
    +    }
    +
    +    }.execute(id);
    +}
    +...
    +
    +public void dump (List 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() {
    +    @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 new file mode 100644 index 000000000000..30555967be38 --- /dev/null +++ b/docs/html/training/cloudsync/backupapi.jd @@ -0,0 +1,193 @@ +page.title=Using the Backup API +parent.title=Syncing to the Cloud +parent.link=index.html + +trainingnavtop=true +previous.title=Syncing with App Engine +previous.link=aesync.html + +@jd:body + + + +

    When a user purchases a new device or resets their existing one, they might +expect that when Google Play restores your app back to their device during the +initial setup, the previous data associated with the app restores as well. By +default, that doesn't happen and all the user's accomplishments or settings in +your app are lost.

    +

    For situations where the volume of data is relatively light (less than a +megabyte), like the user's preferences, notes, game high scores or other +stats, the Backup API provides a lightweight solution. This lesson walks you +through integrating the Backup API into your application, and restoring data to +new devices using the Backup API.

    + +

    Register for the Android Backup Service

    +

    This lesson requires the use of the Android Backup + Service, which requires registration. Go ahead and register here. Once +that's done, the service pre-populates an XML tag for insertion in your Android +Manifest, which looks like this:

    +
    +<meta-data android:name="com.google.android.backup.api_key"
    +android:value="ABcDe1FGHij2KlmN3oPQRs4TUvW5xYZ" />
    +
    +

    Note that each backup key works with a specific package name. If you have +different applications, register separate keys for each one.

    + + +

    Configure Your Manifest

    +

    Use of the Android Backup Service requires two additions to your application +manifest. First, declare the name of the class that acts as your backup agent, +then add the snippet above as a child element of the Application tag. Assuming +your backup agent is going to be called {@code TheBackupAgent}, here's an example of +what the manifest looks like with this tag included:

    + +
    +<application android:label="MyApp"
    +             android:backupAgent="TheBackupAgent">
    +    ...
    +    <meta-data android:name="com.google.android.backup.api_key"
    +    android:value="ABcDe1FGHij2KlmN3oPQRs4TUvW5xYZ" />
    +    ...
    +</application>
    +
    +

    Write Your Backup Agent

    +

    The easiest way to create your backup agent is by extending the wrapper class +{@link android.app.backup.BackupAgentHelper}. Creating this helper class is +actually a very simple process. Just create a class with the same name as you +used in the manifest in the previous step (in this example, {@code +TheBackupAgent}), +and extend {@code BackupAgentHelper}. Then override the {@link +android.app.backup.BackupAgent#onCreate()}.

    + +

    Inside the {@link android.app.backup.BackupAgent#onCreate()} method, create a {@link +android.app.backup.BackupHelper}. These helpers are +specialized classes for backing up certain kinds of data. The Android framework +currently includes two such helpers: {@link +android.app.backup.FileBackupHelper} and {@link +android.app.backup.SharedPreferencesBackupHelper}. After you create the helper +and point it at the data you want to back up, just add it to the +BackupAgentHelper using the {@link android.app.backup.BackupAgentHelper#addHelper(String, BackupHelper) addHelper()} +method, adding a key which is used to +retrieve the data later. In most cases the entire +implementation is perhaps 10 lines of code.

    + +

    Here's an example that backs up a high scores file.

    + +
    + import android.app.backup.BackupAgentHelper;
    + import android.app.backup.FileBackupHelper;
    +
    +
    + public class TheBackupAgent extends BackupAgentHelper {
    +    // The name of the SharedPreferences file
    +    static final String HIGH_SCORES_FILENAME = "scores";
    +
    +    // A key to uniquely identify the set of backup data
    +    static final String FILES_BACKUP_KEY = "myfiles";
    +
    +    // Allocate a helper and add it to the backup agent
    +    @Override
    +    void onCreate() {
    +        FileBackupHelper helper = new FileBackupHelper(this, HIGH_SCORES_FILENAME);
    +        addHelper(FILES_BACKUP_KEY, helper);
    +    }
    +}
    +
    +

    For added flexibility, {@link android.app.backup.FileBackupHelper}'s +constructor can take a variable number of filenames. You could just as easily +have backed up both a high scores file and a game progress file just by adding +an extra parameter, like this:

    +
    +    @Override
    +    void onCreate() {
    +        FileBackupHelper helper = new FileBackupHelper(this, HIGH_SCORES_FILENAME, PROGRESS_FILENAME);
    +        addHelper(FILES_BACKUP_KEY, helper);
    +    }
    +
    +

    Backing up preferences is similarly easy. Create a {@link +android.app.backup.SharedPreferencesBackupHelper} the same way you did a {@link +android.app.backup.FileBackupHelper}. In this case, instead of adding filenames +to the constructor, add the names of the shared preference groups being used by +your application. Here's an example of how your backup agent helper might look if +high scores are implemented as preferences instead of a flat file:

    + +
    + import android.app.backup.BackupAgentHelper;
    + import android.app.backup.SharedPreferencesBackupHelper;
    +
    + public class TheBackupAgent extends BackupAgentHelper {
    +     // The names of the SharedPreferences groups that the application maintains.  These
    +     // are the same strings that are passed to getSharedPreferences(String, int).
    +     static final String PREFS_DISPLAY = "displayprefs";
    +     static final String PREFS_SCORES = "highscores";
    +
    +     // An arbitrary string used within the BackupAgentHelper implementation to
    +     // identify the SharedPreferencesBackupHelper's data.
    +     static final String MY_PREFS_BACKUP_KEY = "myprefs";
    +
    +     // Simply allocate a helper and install it
    +     void onCreate() {
    +         SharedPreferencesBackupHelper helper =
    +                 new SharedPreferencesBackupHelper(this, PREFS_DISPLAY, PREFS_SCORES);
    +         addHelper(MY_PREFS_BACKUP_KEY, helper);
    +     }
    + }
    +
    + +

    You can add as many backup helper instances to your backup agent helper as you +like, but remember that you only need one of each type. One {@link +android.app.backup.FileBackupHelper} handles all the files that you need to back up, and one +{@link android.app.backup.SharedPreferencesBackupHelper} handles all the shared +preferencegroups you need backed up. +

    + + +

    Request a Backup

    +

    In order to request a backup, just create an instance of the {@link +android.app.backup.BackupManager}, and call it's {@link +android.app.backup.BackupManager#dataChanged()} method.

    + +
    + import android.app.backup.BackupManager;
    + ...
    +
    + public void requestBackup() {
    +   BackupManager bm = new BackupManager(this);
    +   bm.dataChanged();
    + }
    +
    + +

    This call notifies the backup manager that there is data ready to be backed +up to the cloud. At some point in the future, the backup manager then calls +your backup agent's {@link +android.app.backup.BackupAgent#onBackup(ParcelFileDescriptor, BackupDataOutput, +ParcelFileDescriptor) onBackup()} method. You can make +the call whenever your data has changed, without having to worry about causing +excessive network activity. If you request a backup twice before a backup +occurs, the backup only occurs once.

    + + +

    Restore from a Backup

    +

    Typically you shouldn't ever have to manually request a restore, as it +happens automatically when your application is installed on a device. However, +if it is necessary to trigger a manual restore, just call the +{@link android.app.backup.BackupManager#requestRestore(RestoreObserver) requestRestore()} method.

    diff --git a/docs/html/training/cloudsync/index.jd b/docs/html/training/cloudsync/index.jd new file mode 100644 index 000000000000..e53844b5015c --- /dev/null +++ b/docs/html/training/cloudsync/index.jd @@ -0,0 +1,34 @@ +page.title=Syncing to the Cloud + +trainingnavtop=true +startpage=true +next.title=Syncing with App Engine +next.link=aesync.html + +@jd:body + +

    By providing powerful APIs for internet connectivity, the Android framework +helps you build rich cloud-enabled apps that sync their data to a remote web +service, making sure all your devices always stay in sync, and your valuable +data is always backed up to the cloud.

    + +

    This class covers different strategies for cloud enabled applications. It +covers syncing data with the cloud using your own back-end web application, and +backing up data using the cloud so that users can restore their data when +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
    +
    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
    +
    + -- cgit v1.2.3-59-g8ed1b