diff --git a/docs/downloads/training/BasicSyncAdapter.zip b/docs/downloads/training/BasicSyncAdapter.zip new file mode 100644 index 0000000000000..25aa8bb53d594 Binary files /dev/null and b/docs/downloads/training/BasicSyncAdapter.zip differ diff --git a/docs/html/training/sync-adapters/creating-authenticator.jd b/docs/html/training/sync-adapters/creating-authenticator.jd new file mode 100644 index 0000000000000..1b272e7b59940 --- /dev/null +++ b/docs/html/training/sync-adapters/creating-authenticator.jd @@ -0,0 +1,290 @@ +page.title=Creating a Stub Authenticator + +trainingnavtop=true +@jd:body + + +
BasicSyncAdapter.zip
++ The sync adapter framework assumes that your sync adapter transfers data between device storage + associated with an account and server storage that requires login access. For this reason, the + framework expects you to provide a component called an authenticator as part of your sync + adapter. This component plugs into the Android accounts and authentication framework and + provides a standard interface for handling user credentials such as login information. +
++ Even if your app doesn't use accounts, you still need to provide an authenticator component. + If you don't use accounts or server login, the information handled by the authenticator is + ignored, so you can provide an authenticator component that contains stub method + implementations. You also need to provide a bound {@link android.app.Service} that + allows the sync adapter framework to call the authenticator's methods. +
++ This lesson shows you how to define all the parts of a stub authenticator that you need to + satisfy the requirements of the sync adapter framework. If you need to provide a real + authenticator that handles user accounts, read the reference documentation for + {@link android.accounts.AbstractAccountAuthenticator}. +
+ ++ To add a stub authenticator component to your app, create a class that extends + {@link android.accounts.AbstractAccountAuthenticator}, and then stub out the required methods, + either by returning {@code null} or by throwing an exception. +
++ The following snippet shows an example of a stub authenticator class: +
+
+/*
+ * Implement AbstractAccountAuthenticator and stub out all
+ * of its methods
+ */
+public class Authenticator extends AbstractAccountAuthenticator {
+ // Simple constructor
+ public Authenticator(Context context) {
+ super(context);
+ }
+ // Editing properties is not supported
+ @Override
+ public Bundle editProperties(
+ AccountAuthenticatorResponse r, String s) {
+ throw new UnsupportedOperationException();
+ }
+ // Don't add additional accounts
+ @Override
+ public Bundle addAccount(
+ AccountAuthenticatorResponse r,
+ String s,
+ String s2,
+ String[] strings,
+ Bundle bundle) throws NetworkErrorException {
+ return null;
+ }
+ // Ignore attempts to confirm credentials
+ @Override
+ public Bundle confirmCredentials(
+ AccountAuthenticatorResponse r,
+ Account account,
+ Bundle bundle) throws NetworkErrorException {
+ return null;
+ }
+ // Getting an authentication token is not supported
+ @Override
+ public Bundle getAuthToken(
+ AccountAuthenticatorResponse r,
+ Account account,
+ String s,
+ Bundle bundle) throws NetworkErrorException {
+ throw new UnsupportedOperationException();
+ }
+ // Getting a label for the auth token is not supported
+ @Override
+ public String getAuthTokenLabel(String s) {
+ throw new UnsupportedOperationException();
+ }
+ // Updating user credentials is not supported
+ @Override
+ public Bundle updateCredentials(
+ AccountAuthenticatorResponse r,
+ Account account,
+ String s, Bundle bundle) throws NetworkErrorException {
+ throw new UnsupportedOperationException();
+ }
+ // Checking features for the account is not supported
+ @Override
+ public Bundle hasFeatures(
+ AccountAuthenticatorResponse r,
+ Account account, String[] strings) throws NetworkErrorException {
+ throw new UnsupportedOperationException();
+ }
+}
+
++ In order for the sync adapter framework to access your authenticator, you must create a bound + Service for it. This service provides an Android binder object that allows the framework + to call your authenticator and pass data between the authenticator and the framework. +
++ Since the framework starts this {@link android.app.Service} the first time it needs to + access the authenticator, you can also use the service to instantiate the authenticator, + by calling the authenticator constructor in the + {@link android.app.Service#onCreate Service.onCreate()} method of the service. +
++ The following snippet shows you how to define the bound {@link android.app.Service}: +
+
+/**
+ * A bound Service that instantiates the authenticator
+ * when started.
+ */
+public class AuthenticatorService extends Service {
+ ...
+ // Instance field that stores the authenticator object
+ private Authenticator mAuthenticator;
+ @Override
+ public void onCreate() {
+ // Create a new authenticator object
+ mAuthenticator = new Authenticator(this);
+ }
+ /*
+ * When the system binds to this Service to make the RPC call
+ * return the authenticator's IBinder.
+ */
+ @Override
+ public IBinder onBind(Intent intent) {
+ return mAuthenticator.getIBinder();
+ }
+}
+
+
++ To plug your authenticator component into the sync adapter and account frameworks, you need to + provide these framework with metadata that describes the component. This metadata declares the + account type you've created for your sync adapter and declares user interface elements + that the system displays if you want to make your account type visible to the user. Declare this + metadata in a XML file stored in the {@code /res/xml/} directory in your app project. + You can give any name to the file, although it's usually called {@code authenticator.xml}. +
+
+ This XML file contains a single element <account-authenticator> that
+ has the following attributes:
+
android:accountType
+ + If your server doesn't require login, you still have to provide an account type. For the + value, use a domain name that you control. While the framework uses it to manage your + sync adapter, the value is not sent to your server. +
+android:icon
+ android:userVisible="true" in res/xml/syncadapter.xml,
+ then you must provide this icon resource. It appears in the Accounts section of
+ the system's Settings app.
+ android:smallIcon
+ android:icon in the Accounts section of the system's Settings app,
+ depending on the screen size.
+ android:label
+ android:userVisible="true" in
+ res/xml/syncadapter.xml, then you should provide this string. It appears in the
+ Accounts section of the system's Settings app, next to the icon you define for the
+ authenticator.
+ + The following snippet shows the XML file for the authenticator you created previously: +
+the +<?xml version="1.0" encoding="utf-8"?> +<account-authenticator + xmlns:android="http://schemas.android.com/apk/res/android" + android:accountType="example.com" + android:icon="@drawable/ic_launcher" + android:smallIcon="@drawable/ic_launcher" + android:label="@string/app_name"/> ++ +
+ In a previous step, you created a bound {@link android.app.Service} that links the authenticator
+ to the sync adapter framework. To identify this service to the system, declare it in your app
+ manifest by adding the following
+ <service>
+ element as a child element of
+<application>:
+
+ <service + android:name="com.example.android.syncadapter.AuthenticatorService"> + <intent-filter> + <action android:name="android.accounts.AccountAuthenticator"/> + </intent-filter> + <meta-data + android:name="android.accounts.AccountAuthenticator" + android:resource="@xml/authenticator" /> + </service> ++
+ The
+<intent-filter>
+ element sets up a filter that's triggered by the intent action
+ {@code android.accounts.AccountAuthenticator}, which sent by the system to run the
+ authenticator. When the filter is triggered, the system starts {@code AuthenticatorService},
+ the bound {@link android.app.Service} you have provided to wrap the authenticator.
+
+ The
+<meta-data>
+ element declares the metadata for the authenticator. The
+android:name
+ attribute links the meta-data to the authentication framework. The
+android:resource
+ element specifies the name of the authenticator metadata file you created previously.
+
+ Besides an authenticator, a sync adapter also requires a content provider. If your app doesn't + use a content provider already, go to the next lesson to learn how to create a stub content + provider; otherwise, go to the lesson Creating a Sync Adapter. +
diff --git a/docs/html/training/sync-adapters/creating-stub-provider.jd b/docs/html/training/sync-adapters/creating-stub-provider.jd new file mode 100644 index 0000000000000..8f6eba037ef7b --- /dev/null +++ b/docs/html/training/sync-adapters/creating-stub-provider.jd @@ -0,0 +1,203 @@ +page.title=Creating a Stub Content Provider + +trainingnavtop=true +@jd:body + + +BasicSyncAdapter.zip
++ The sync adapter framework is designed to work with device data managed by the flexible and + highly secure content provider framework. For this reason, the sync adapter framework expects + that an app that uses the framework has already defined a content provider for its local data. + If the sync adapter framework tries to run your sync adapter, and your app doesn't have a + content provider, your sync adapter crashes. +
++ If you're developing a new app that transfers data from a server to the device, you should + strongly consider storing the local data in a content provider. Besides their importance for + sync adapters, content providers offer a variety of security benefits and are specifically + designed to handle data storage on Android systems. To learn more about creating a content + provider, see Creating a Content Provider. +
++ However, if you're already storing local data in another form, you can still use a sync + adapter to handle data transfer. To satisfy the sync adapter framework requirement for a + content provider, add a stub content provider to your app. A stub provider implements the + content provider class, but all of its required methods return {@code null} or {@code 0}. If you + add a stub provider, you can then use a sync adapter to transfer data from any storage + mechanism you choose. +
++ If you already have a content provider in your app, you don't need a stub content provider. + In that case, you can skip this lesson and proceed to the lesson + Creating a Sync Adapter. If you don't yet have a + content provider, this lesson shows you how to add a stub content provider that allows you to + plug your sync adapter into the framework. +
++ To create a stub content provider for your app, extend the class + {@link android.content.ContentProvider} and stub out its required methods. The following + snippet shows you how to create the stub provider: +
+
+/*
+ * Define an implementation of ContentProvider that stubs out
+ * all methods
+ */
+public class StubProvider extends ContentProvider {
+ /*
+ * Always return true, indicating that the
+ * provider loaded correctly.
+ */
+ @Override
+ public boolean onCreate() {
+ return true;
+ }
+ /*
+ * Return an empty String for MIME type
+ */
+ @Override
+ public String getType() {
+ return new String();
+ }
+ /*
+ * query() always returns no results
+ *
+ */
+ @Override
+ public Cursor query(
+ Uri uri,
+ String[] projection,
+ String selection,
+ String[] selectionArgs,
+ String sortOrder) {
+ return null;
+ }
+ /*
+ * insert() always returns null (no URI)
+ */
+ @Override
+ public Uri insert(Uri uri, ContentValues values) {
+ return null;
+ }
+ /*
+ * delete() always returns "no rows affected" (0)
+ */
+ @Override
+ public int delete(Uri uri, String selection, String[] selectionArgs) {
+ return 0;
+ }
+ /*
+ * update() always returns "no rows affected" (0)
+ */
+ public int update(
+ Uri uri,
+ ContentValues values,
+ String selection,
+ String[] selectionArgs) {
+ return 0;
+ }
+}
+
+
+ The sync adapter framework verifies that your app has a content provider by checking that your
+ app has declared a provider in its app manifest. To declare the stub provider in the
+ manifest, add a <provider> element with the following attributes:
+
android:name="com.example.android.datasync.provider.StubProvider"
+ android:authorities="com.example.android.datasync.provider"
+ android:exported="false"
+ android:syncable="true"
+
+ The following snippet shows you how to add the
+ <provider> element to the app manifest:
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.example.android.network.sync.BasicSyncAdapter" + android:versionCode="1" + android:versionName="1.0" > + <application + android:allowBackup="true" + android:icon="@drawable/ic_launcher" + android:label="@string/app_name" + android:theme="@style/AppTheme" > + ... + <provider + android:name="com.example.android.datasync.provider.StubProvider" + android:authorities="com.example.android.datasync.provider" + android:export="false" + android:syncable="true"/> + ... + </application> +</manifest> ++
+ Now that you have created the dependencies required by the sync adapter framework, you can + create the component that encapsulates your data transfer code. This component is called a + sync adapter. The next lesson shows you how to add this component to your app. +
diff --git a/docs/html/training/sync-adapters/creating-sync-adapter.jd b/docs/html/training/sync-adapters/creating-sync-adapter.jd new file mode 100644 index 0000000000000..7c59c8c69703c --- /dev/null +++ b/docs/html/training/sync-adapters/creating-sync-adapter.jd @@ -0,0 +1,658 @@ +page.title=Creating a Sync Adapter + +trainingnavtop=true +@jd:body + +BasicSyncAdapter.zip
++ The sync adapter component in your app encapsulates the code for the tasks that transfer + data between the device and a server. Based on the scheduling and triggers you provide in + your app, the sync adapter framework runs the code in the sync adapter component. To add a + sync adapter component to your app, you need to add the following pieces: +
+ This lesson shows you how to define these elements. +
++ In this part of the lesson you learn how to create the sync adapter class that encapsulates the + data transfer code. Creating the class includes extending the sync adapter base class, defining + constructors for the class, and implementing the method where you define the data transfer + tasks. +
++ To create the sync adapter component, start by extending + {@link android.content.AbstractThreadedSyncAdapter} and writing its constructors. Use the + constructors to run setup tasks each time your sync adapter component is created from + scratch, just as you use {@link android.app.Activity#onCreate Activity.onCreate()} to set up an + activity. For example, if your app uses a content provider to store data, use the constructors + to get a {@link android.content.ContentResolver} instance. Since a second form of the + constructor was added in Android platform version 3.0 to support the {@code parallelSyncs} + argument, you need to create two forms of the constructor to maintain compatibility. +
++ Note: The sync adapter framework is designed to work with sync adapter + components that are singleton instances. Instantiating the sync adapter component is covered + in more detail in the section + Bind the Sync Adapter to the Framework. +
++ The following example shows you how to implement + {@link android.content.AbstractThreadedSyncAdapter}and its constructors: +
+
+/**
+ * Handle the transfer of data between a server and an
+ * app, using the Android sync adapter framework.
+ */
+public class SyncAdapter extends AbstractThreadedSyncAdapter {
+ ...
+ // Global variables
+ // Define a variable to contain a content resolver instance
+ ContentResolver mContentResolver;
+ /**
+ * Set up the sync adapter
+ */
+ public SyncAdapter(Context context, boolean autoInitialize) {
+ super(context, autoInitialize);
+ /*
+ * If your app uses a content resolver, get an instance of it
+ * from the incoming Context
+ */
+ mContentResolver = context.getContentResolver();
+ }
+ ...
+ /**
+ * Set up the sync adapter. This form of the
+ * constructor maintains compatibility with Android 3.0
+ * and later platform versions
+ */
+ public SyncAdapter(
+ Context context,
+ boolean autoInitialize,
+ boolean allowParallelSyncs) {
+ super(context, autoInitialize, allowParallelSyncs);
+ /*
+ * If your app uses a content resolver, get an instance of it
+ * from the incoming Context
+ */
+ mContentResolver = context.getContentResolver();
+ ...
+ }
+
++ The sync adapter component does not automatically do data transfer. Instead, it + encapsulates your data transfer code, so that the sync adapter framework can run the + data transfer in the background, without involvement from your app. When the framework is ready + to sync your application's data, it invokes your implementation of the method + {@link android.content.AbstractThreadedSyncAdapter#onPerformSync onPerformSync()}. +
++ To facilitate the transfer of data from your main app code to the sync adapter component, + the sync adapter framework calls + {@link android.content.AbstractThreadedSyncAdapter#onPerformSync onPerformSync()} with the + following arguments: +
++ The following snippet shows the overall structure of + {@link android.content.AbstractThreadedSyncAdapter#onPerformSync onPerformSync()}: +
+
+ /*
+ * Specify the code you want to run in the sync adapter. The entire
+ * sync adapter runs in a background thread, so you don't have to set
+ * up your own background processing.
+ */
+ @Override
+ public void onPerformSync(
+ Account account,
+ Bundle extras,
+ String authority,
+ ContentProviderClient provider,
+ SyncResult syncResult) {
+ /*
+ * Put the data transfer code here.
+ */
+ ...
+ }
+
++ While the actual implementation of + {@link android.content.AbstractThreadedSyncAdapter#onPerformSync onPerformSync()} is specific to + your app's data synchronization requirements and server connection protocols, there are a few + general tasks your implementation should perform: +
++ Note: The sync adapter framework runs + {@link android.content.AbstractThreadedSyncAdapter#onPerformSync onPerformSync()} on a + background thread, so you don't have to set up your own background processing. +
++ In addition to your sync-related tasks, you should try to combine your regular + network-related tasks and add them to + {@link android.content.AbstractThreadedSyncAdapter#onPerformSync onPerformSync()}. + By concentrating all of your network tasks in this method, you conserve the battery power that's + needed to start and stop the network interfaces. To learn more about making network access more + efficient, see the training class Transferring Data Without Draining the Battery, which describes several network access + tasks you can include in your data transfer code. +
++ You now have your data transfer code encapsulated in a sync adapter component, but you have + to provide the framework with access to your code. To do this, you need to create a bound + {@link android.app.Service} that passes a special Android binder object from the sync adapter + component to the framework. With this binder object, the framework can invoke the + {@link android.content.AbstractThreadedSyncAdapter#onPerformSync onPerformSync()} method and + pass data to it. +
++ Instantiate your sync adapter component as a singleton in the + {@link android.app.Service#onCreate onCreate()} method of the service. By instantiating + the component in {@link android.app.Service#onCreate onCreate()}, you defer + creating it until the service starts, which happens when the framework first tries to run your + data transfer. You need to instantiate the component in a thread-safe manner, in case the sync + adapter framework queues up multiple executions of your sync adapter in response to triggers or + scheduling. +
++ For example, the following snippet shows you how to create a class that implements the + bound {@link android.app.Service}, instantiates your sync adapter component, and gets the + Android binder object: +
+
+package com.example.android.syncadapter;
+/**
+ * Define a Service that returns an {@link android.os.IBinder} for the
+ * sync adapter class, allowing the sync adapter framework to call
+ * onPerformSync().
+ */
+public class SyncService extends Service {
+ // Storage for an instance of the sync adapter
+ private static SyncAdapter sSyncAdapter = null;
+ // Object to use as a thread-safe lock
+ private static final Object sSyncAdapterLock = new Object();
+ /*
+ * Instantiate the sync adapter object.
+ */
+ @Override
+ public void onCreate() {
+ /*
+ * Create the sync adapter as a singleton.
+ * Set the sync adapter as syncable
+ * Disallow parallel syncs
+ */
+ synchronized (sSyncAdapterLock) {
+ if (sSyncAdapter == null) {
+ sSyncAdapter = new SyncAdapter(getApplicationContext(), true);
+ }
+ }
+ }
+ /**
+ * Return an object that allows the system to invoke
+ * the sync adapter.
+ *
+ */
+ @Override
+ public IBinder onBind(Intent intent) {
+ /*
+ * Get the object that allows external processes
+ * to call onPerformSync(). The object is created
+ * in the base class code when the SyncAdapter
+ * constructors call super()
+ */
+ return sSyncAdapter.getSyncAdapterBinder();
+ }
+}
+
++ Note: To see a more detailed example of a bound service for a sync adapter, + see the sample app. +
++ The sync adapter framework requires each sync adapter to have an account type. You declared + the account type value in the section + Add the Authenticator Metadata File. Now you have to set up this account type in the + Android system. To set up the account type, add a dummy account that uses the account type + by calling {@link android.accounts.AccountManager#addAccountExplicitly addAccountExplicitly()}. +
++ The best place to call the method is in the + {@link android.support.v4.app.FragmentActivity#onCreate onCreate()} method of your app's + opening activity. The following code snippet shows you how to do this: +
+
+public class MainActivity extends FragmentActivity {
+ ...
+ ...
+ // Constants
+ // The authority for the sync adapter's content provider
+ public static final String AUTHORITY = "com.example.android.datasync.provider"
+ // An account type, in the form of a domain name
+ public static final String ACCOUNT_TYPE = "example.com";
+ // The account name
+ public static final String ACCOUNT = "dummyaccount";
+ // Instance fields
+ Account mAccount;
+ ...
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ ...
+ // Create the dummy account
+ mAccount = CreateSyncAccount(this);
+ ...
+ }
+ ...
+ /**
+ * Create a new dummy account for the sync adapter
+ *
+ * @param context The application context
+ */
+ public static Account CreateSyncAccount(Context context) {
+ // Create the account type and default account
+ Account newAccount = new Account(
+ ACCOUNT, ACCOUNT_TYPE);
+ // Get an instance of the Android account manager
+ AccountManager accountManager =
+ (AccountManager) context.getSystemService(
+ ACCOUNT_SERVICE);
+ /*
+ * Add the account and account type, no password or user data
+ * If successful, return the Account object, otherwise report an error.
+ */
+ if (accountManager.addAccountExplicitly(newAccount, null, null))) {
+ /*
+ * If you don't set android:syncable="true" in
+ * in your <provider> element in the manifest,
+ * then call context.setIsSyncable(account, AUTHORITY, 1)
+ * here.
+ */
+ } else {
+ /*
+ * The account exists or some other error occurred. Log this, report it,
+ * or handle it internally.
+ */
+ }
+ }
+ ...
+}
+
++ To plug your sync adapter component into the framework, you need to provide the framework + with metadata that describes the component and provides additional flags. The metadata specifies + the account type you've created for your sync adapter, declares a content provider authority + associated with your app, controls a part of the system user interface related to sync adapters, + and declares other sync-related flags. Declare this metadata in a special XML file stored in + the {@code /res/xml/} directory in your app project. You can give any name to the file, + although it's usually called {@code syncadapter.xml}. +
+
+ This XML file contains a single XML element <sync-adapter> that has the
+ following attributes:
+
android:contentAuthorityandroid:authorities
+ in the <provider> element you added to your app manifest. This attribute is
+ described in more detail in the section
+ Declare the Provider in the Manifest.
+ android:authorities
+ attribute of the <provider> element that declares your provider in your app manifest.
+ android:accountType+ The following example shows the XML for a sync adapter that uses a single dummy account and + only does downloads. +
++<?xml version="1.0" encoding="utf-8"?> +<sync-adapter + xmlns:android="http://schemas.android.com/apk/res/android" + android:contentAuthority="com.example.android.datasync.provider" + android:accountType="com.android.example.datasync" + android:userVisible="false" + android:supportsUploading="false" + android:allowParallelSyncs="false" + android:isAlwaysSyncable="true"/> ++ +
+ Once you've added the sync adapter component to your app, you have to request permissions + related to using the component, and you have to declare the bound {@link android.app.Service} + you've added. +
++ Since the sync adapter component runs code that transfers data between the network and the + device, you need to request permission to access the Internet. In addition, your app needs + to request permission to read and write sync adapter settings, so you can control the sync + adapter programmatically from other components in your app. You also need to request a + special permission that allows your app to use the authenticator component you created + in the lesson Creating a Stub Authenticator. +
+
+ To request these permissions, add the following to your app manifest as child elements of
+<manifest>:
+
+ The following snippet shows how to add the permissions: +
++<manifest> +... + <uses-permission + android:name="android.permission.INTERNET"/> + <uses-permission + android:name="android.permission.READ_SYNC_SETTINGS"/> + <uses-permission + android:name="android.permission.WRITE_SYNC_SETTINGS"/> + <uses-permission + android:name="android.permission.AUTHENTICATE_ACCOUNTS"/> +... +</manifest> ++
+ Finally, to declare the bound {@link android.app.Service} that the framework uses to
+ interact with your sync adapter, add the following XML to your app manifest as a child element
+ of <application>:
+
+ <service + android:name="com.example.android.datasync.SyncService" + android:exported="true" + android:process=":sync"> + <intent-filter>com.example.android.datasync.provider + <action android:name="android.content.SyncAdapter"/> + </intent-filter> + <meta-data android:name="android.content.SyncAdapter" + android:resource="@xml/syncadapter" /> + </service> ++
+ The
+<intent-filter>
+ element sets up a filter that's triggered by the intent action
+ {@code android.content.SyncAdapter}, sent by the system to run the sync adapter. When the filter
+ is triggered, the system starts the bound service you've created, which in this example is
+ {@code SyncService}. The attribute
+android:exported="true"
+ allows processes other than your app (including the system) to access the
+ {@link android.app.Service}. The attribute
+android:process=":sync"
+ tells the system to run the {@link android.app.Service} in a global shared process named
+ {@code sync}. If you have multiple sync adapters in your app they can share this process,
+ which reduces overhead.
+
+ The
+<meta-data>
+ element provides provides the name of the sync adapter metadata XML file you created previously.
+ The
+android:name
+ attribute indicates that this metadata is for the sync adapter framework. The
+android:resource
+ element specifies the name of the metadata file.
+
+ You now have all of the components for your sync adapter. The next lesson shows you how to + tell the sync adapter framework to run your sync adapter, either in response to an event or on + a regular schedule. +
diff --git a/docs/html/training/sync-adapters/index.jd b/docs/html/training/sync-adapters/index.jd new file mode 100644 index 0000000000000..1f7977b2d4fcc --- /dev/null +++ b/docs/html/training/sync-adapters/index.jd @@ -0,0 +1,135 @@ +page.title=Transferring Data Using Sync Adapters + +trainingnavtop=true +startpage=true + + +@jd:body + +BasicSyncAdapter.zip
++ Synchronizing data between an Android device and web servers can make your application + significantly more useful and compelling for your users. For example, transferring data to a web + server makes a useful backup, and transferring data from a server makes it available to the user + even when the device is offline. In some cases, users may find it easier to enter and edit their + data in a web interface and then have that data available on their device, or they may want to + collect data over time and then upload it to a central storage area. +
++ Although you can design your own system for doing data transfers in your app, you should + consider using Android's sync adapter framework. This framework helps manage and automate data + transfers, and coordinates synchronization operations across different apps. When you use + this framework, you can take advantage of several features that aren't available to data + transfer schemes you design yourself: +
++ This class shows you how to create a sync adapter and the bound {@link android.app.Service} that + wraps it, how to provide the other components that help you plug the sync adapter into the + framework, and how to run the sync adapter to run in various ways. +
++ Note: Sync adapters run asynchronously, so you should use them with the + expectation that they transfer data regularly and efficiently, but not instantaneously. If + you need to do real-time data transfer, you should do it in an {@link android.os.AsyncTask} or + an {@link android.app.IntentService}. +
+BasicSyncAdapter.zip
++ In the previous lessons in this class, you learned how to create a sync adapter component that + encapsulates data transfer code, and how to add the additional components that allow you to + plug the sync adapter into the system. You now have everything you need to install an app that + includes a sync adapter, but none of the code you've seen actually runs the sync adapter. +
++ You should try to run your sync adapter based on a schedule or as the indirect result of some + event. For example, you may want your sync adapter to run on a regular schedule, either after a + certain period of time or at a particular time of the day. You may also want to run your sync + adapter when there are changes to data stored on the device. You should avoid running your + sync adapter as the direct result of a user action, because by doing this you don't get the full + benefit of the sync adapter framework's scheduling ability. For example, you should avoid + providing a refresh button in your user interface. +
++ You have the following options for running your sync adapter: +
++ The rest of this lesson describes each of the options in more detail. +
++ If your app transfers data from a server and the server data changes frequently, you can use + a sync adapter to do downloads in response to data changes. To run the sync adapter, have + the server send a special message to a {@link android.content.BroadcastReceiver} in your app. + In response to this message, call {@link android.content.ContentResolver#requestSync + ContentResolver.requestSync()} to signal the sync adapter framework to run your + sync adapter. +
++ Google Cloud Messaging (GCM) provides both the + server and device components you need to make this messaging system work. Using GCM to trigger + transfers is more reliable and more efficient than polling servers for status. While polling + requires a {@link android.app.Service} that is always active, GCM uses a + {@link android.content.BroadcastReceiver} that's activated when a message arrives. While polling + at regular intervals uses battery power even if no updates are available, GCM only sends + messages when needed. +
++ Note: If you use GCM to trigger your sync adapter via a broadcast to all + devices where your app is installed, remember that they receive your message at + roughly the same time. This situation can cause multiple instance of your sync adapter to run + at the same time, causing server and network overload. To avoid this situation for a broadcast + to all devices, you should consider deferring the start of the sync adapter for a period + that's unique for each device. +
+ The following code snippet shows you how to run + {@link android.content.ContentResolver#requestSync requestSync()} in response to an + incoming GCM message: +
+
+public class GcmBroadcastReceiver extends BroadcastReceiver {
+ ...
+ // Constants
+ // Content provider authority
+ public static final String AUTHORITY = "com.example.android.datasync.provider"
+ // Account type
+ public static final String ACCOUNT_TYPE = "com.example.android.datasync";
+ // Account
+ public static final String ACCOUNT = "default_account";
+ // Incoming Intent key for extended data
+ public static final String KEY_SYNC_REQUEST =
+ "com.example.android.datasync.KEY_SYNC_REQUEST";
+ ...
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ // Get a GCM object instance
+ GoogleCloudMessaging gcm =
+ GoogleCloudMessaging.getInstance(context);
+ // Get the type of GCM message
+ String messageType = gcm.getMessageType(intent);
+ /*
+ * Test the message type and examine the message contents.
+ * Since GCM is a general-purpose messaging system, you
+ * may receive normal messages that don't require a sync
+ * adapter run.
+ * The following code tests for a a boolean flag indicating
+ * that the message is requesting a transfer from the device.
+ */
+ if (GoogleCloudMessaging.MESSAGE_TYPE_MESSAGE.equals(messageType)
+ &&
+ intent.getBooleanExtra(KEY_SYNC_REQUEST)) {
+ /*
+ * Signal the framework to run your sync adapter. Assume that
+ * app initialization has already created the account.
+ */
+ ContentResolver.requestSync(ACCOUNT, AUTHORITY, null);
+ ...
+ }
+ ...
+ }
+ ...
+}
+
++ If your app collects data in a content provider, and you want to update the server whenever + you update the provider, you can set up your app to run your sync adapter automatically. To do + this, you register an observer for the content provider. When data in your content provider + changes, the content provider framework calls the observer. In the observer, call + {@link android.content.ContentResolver#requestSync requestSync()} to tell the framework to run + your sync adapter. +
++ Note: If you're using a stub content provider, you don't have any data in + the content provider and {@link android.database.ContentObserver#onChange onChange()} is + never called. In this case, you have to provide your own mechanism for detecting changes to + device data. This mechanism is also responsible for calling + {@link android.content.ContentResolver#requestSync requestSync()} when the data changes. +
++ To create an observer for your content provider, extend the class + {@link android.database.ContentObserver} and implement both forms of its + {@link android.database.ContentObserver#onChange onChange()} method. In + {@link android.database.ContentObserver#onChange onChange()}, call + {@link android.content.ContentResolver#requestSync requestSync()} to start the sync adapter. +
++ To register the observer, pass it as an argument in a call to + {@link android.content.ContentResolver#registerContentObserver registerContentObserver()}. In + this call, you also have to pass in a content URI for the data you want to watch. The content + provider framework compares this watch URI to content URIs passed in as arguments to + {@link android.content.ContentResolver} methods that modify your provider, such as + {@link android.content.ContentResolver#insert ContentResolver.insert()}. If there's a match, your + implementation of {@link android.database.ContentObserver#onChange ContentObserver.onChange()} + is called. +
+ ++ The following code snippet shows you how to define a {@link android.database.ContentObserver} + that calls {@link android.content.ContentResolver#requestSync requestSync()} when a table + changes: +
+
+public class MainActivity extends FragmentActivity {
+ ...
+ // Constants
+ // Content provider scheme
+ public static final String SCHEME = "content://";
+ // Content provider authority
+ public static final String AUTHORITY = "com.example.android.datasync.provider";
+ // Path for the content provider table
+ public static final String TABLE_PATH = "data_table";
+ // Account
+ public static final String ACCOUNT = "default_account";
+ // Global variables
+ // A content URI for the content provider's data table
+ Uri mUri;
+ // A content resolver for accessing the provider
+ ContentResolver mResolver;
+ ...
+ public class TableObserver extends ContentObserver {
+ /*
+ * Define a method that's called when data in the
+ * observed content provider changes.
+ * This method signature is provided for compatibility with
+ * older platforms.
+ */
+ @Override
+ public void onChange(boolean selfChange) {
+ /*
+ * Invoke the method signature available as of
+ * Android platform version 4.1, with a null URI.
+ */
+ onChange(selfChange, null);
+ }
+ /*
+ * Define a method that's called when data in the
+ * observed content provider changes.
+ */
+ @Override
+ public void onChange(boolean selfChange, Uri changeUri) {
+ /*
+ * Ask the framework to run your sync adapter.
+ * To maintain backward compatibility, assume that
+ * changeUri is null.
+ ContentResolver.requestSync(ACCOUNT, AUTHORITY, null);
+ }
+ ...
+ }
+ ...
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ ...
+ // Get the content resolver object for your app
+ mResolver = getContentResolver();
+ // Construct a URI that points to the content provider data table
+ mUri = new Uri.Builder()
+ .scheme(SCHEME)
+ .authority(AUTHORITY)
+ .path(TABLE_PATH)
+ .build();
+ /*
+ * Create a content observer object.
+ * Its code does not mutate the provider, so set
+ * selfChange to "false"
+ */
+ TableObserver observer = new TableObserver(false);
+ /*
+ * Register the observer for the data table. The table's path
+ * and any of its subpaths trigger the observer.
+ */
+ mResolver.registerContentObserver(mUri, true, observer);
+ ...
+ }
+ ...
+}
+
++ When a network connection is available, the Android system sends out a message + every few seconds to keep the device's TCP/IP connection open. This message also goes to + the {@link android.content.ContentResolver} of each app. By calling + {@link android.content.ContentResolver#setSyncAutomatically setSyncAutomatically()}, + you can run the sync adapter whenever the {@link android.content.ContentResolver} + receives the message. +
++ By scheduling your sync adapter to run when the network message is sent, you ensure that your + sync adapter is always scheduled to run while the network is available. Use this option if you + don't have to force a data transfer in response to data changes, but you do want to ensure + your data is regularly updated. Similarly, you can use this option if you don't want a fixed + schedule for your sync adapter, but you do want it to run frequently. +
++ Since the method + {@link android.content.ContentResolver#setSyncAutomatically setSyncAutomatically()} + doesn't disable {@link android.content.ContentResolver#addPeriodicSync addPeriodicSync()}, your + sync adapter may be triggered repeatedly in a short period of time. If you do want to run + your sync adapter periodically on a regular schedule, you should disable + {@link android.content.ContentResolver#setSyncAutomatically setSyncAutomatically()}. +
++ The following code snippet shows you how to configure your + {@link android.content.ContentResolver} to run your sync adapter in response to a network + message: +
+
+public class MainActivity extends FragmentActivity {
+ ...
+ // Constants
+ // Content provider authority
+ public static final String AUTHORITY = "com.example.android.datasync.provider";
+ // Account
+ public static final String ACCOUNT = "default_account";
+ // Global variables
+ // A content resolver for accessing the provider
+ ContentResolver mResolver;
+ ...
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ ...
+ // Get the content resolver for your app
+ mResolver = getContentResolver();
+ // Turn on automatic syncing for the default account and authority
+ mResolver.setSyncAutomatically(ACCOUNT, AUTHORITY, true);
+ ...
+ }
+ ...
+}
+
++ You can run your sync adapter periodically by setting a period of time to wait between runs, + or by running it at certain times of the day, or both. Running your sync adapter + periodically allows you to roughly match the update interval of your server. +
++ Similarly, you can upload data from the device when your server is relatively idle, by + scheduling your sync adapter to run at night. Most users leave their powered on and plugged in + at night, so this time is usually available. Moreover, the device is not running other tasks at + the same time as your sync adapter. If you take this approach, however, you need to ensure that + each device triggers a data transfer at a slightly different time. If all devices run your + sync adapter at the same time, you are likely to overload your server and cell provider data + networks. +
++ In general, periodic runs make sense if your users don't need instant updates, but expect to + have regular updates. Periodic runs also make sense if you want to balance the availability of + up-to-date data with the efficiency of smaller sync adapter runs that don't over-use device + resources. +
++ To run your sync adapter at regular intervals, call + {@link android.content.ContentResolver#addPeriodicSync addPeriodicSync()}. This schedules your + sync adapter to run after a certain amount of time has elapsed. Since the sync adapter framework + has to account for other sync adapter executions and tries to maximize battery efficiency, the + elapsed time may vary by a few seconds. Also, the framework won't run your sync adapter if the + network is not available. +
++ Notice that {@link android.content.ContentResolver#addPeriodicSync addPeriodicSync()} doesn't + run the sync adapter at a particular time of day. To run your sync adapter at roughly the + same time every day, use a repeating alarm as a trigger. Repeating alarms are described in more + detail in the reference documentation for {@link android.app.AlarmManager}. If you use the + method {@link android.app.AlarmManager#setInexactRepeating setInexactRepeating()} to set + time-of-day triggers that have some variation, you should still randomize the start time to + ensure that sync adapter runs from different devices are staggered. +
++ The method {@link android.content.ContentResolver#addPeriodicSync addPeriodicSync()} doesn't + disable {@link android.content.ContentResolver#setSyncAutomatically setSyncAutomatically()}, + so you may get multiple sync runs in a relatively short period of time. Also, only a few + sync adapter control flags are allowed in a call to + {@link android.content.ContentResolver#addPeriodicSync addPeriodicSync()}; the flags that are + not allowed are described in the referenced documentation for + {@link android.content.ContentResolver#addPeriodicSync addPeriodicSync()}. +
++ The following code snippet shows you how to schedule periodic sync adapter runs: +
+
+public class MainActivity extends FragmentActivity {
+ ...
+ // Constants
+ // Content provider authority
+ public static final String AUTHORITY = "com.example.android.datasync.provider";
+ // Account
+ public static final String ACCOUNT = "default_account";
+ // Sync interval constants
+ public static final long MILLISECONDS_PER_SECOND = 1000L;
+ public static final long SECONDS_PER_MINUTE = 60L;
+ public static final long SYNC_INTERVAL_IN_MINUTES = 60L;
+ public static final long SYNC_INTERVAL =
+ SYNC_INTERVAL_IN_MINUTES *
+ SECONDS_PER_MINUTE *
+ MILLISECONDS_PER_SECOND;
+ // Global variables
+ // A content resolver for accessing the provider
+ ContentResolver mResolver;
+ ...
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ ...
+ // Get the content resolver for your app
+ mResolver = getContentResolver();
+ /*
+ * Turn on periodic syncing
+ */
+ ContentResolver.addPeriodicSync(
+ ACCOUNT,
+ AUTHORITY,
+ null,
+ SYNC_INTERVAL);
+ ...
+ }
+ ...
+}
+
++ Running your sync adapter in response to a user request is the least preferable strategy + for running a sync adapter. The framework is specifically designed to conserve battery power + when it runs sync adapters according to a schedule. Options that run a sync in response to data + changes use battery power effectively, since the power is used to provide new data. +
++ In comparison, allowing users to run a sync on demand means that the sync runs by itself, which + is inefficient use of network and power resources. Also, providing sync on demand leads users to + request a sync even if there's no evidence that the data has changed, and running a sync that + doesn't refresh data is an ineffective use of battery power. In general, your app should either + use other signals to trigger a sync or schedule them at regular intervals, without user input. +
++ However, if you still want to run the sync adapter on demand, set the sync adapter flags for a + manual sync adapter run, then call + {@link android.content.ContentResolver#requestSync ContentResolver.requestSync()}. +
++ Run on demand transfers with the following flags: +
++ The following code snippet shows you how to call + {@link android.content.ContentResolver#requestSync requestSync()} in response to a button + click: +
+
+public class MainActivity extends FragmentActivity {
+ ...
+ // Constants
+ // Content provider authority
+ public static final String AUTHORITY =
+ "com.example.android.datasync.provider"
+ // Account type
+ public static final String ACCOUNT_TYPE = "com.example.android.datasync";
+ // Account
+ public static final String ACCOUNT = "default_account";
+ // Instance fields
+ Account mAccount;
+ ...
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ ...
+ /*
+ * Create the dummy account. The code for CreateSyncAccount
+ * is listed in the lesson Creating a Sync Adapter
+ */
+
+ mAccount = CreateSyncAccount(this);
+ ...
+ }
+ /**
+ * Respond to a button click by calling requestSync(). This is an
+ * asynchronous operation.
+ *
+ * This method is attached to the refresh button in the layout
+ * XML file
+ *
+ * @param v The View associated with the method call,
+ * in this case a Button
+ */
+ public void onRefreshButtonClick(View v) {
+ ...
+ // Pass the settings flags by inserting them in a bundle
+ Bundle settingsBundle = new Bundle();
+ settingsBundle.putBoolean(
+ ContentResolver.SYNC_EXTRAS_MANUAL, true);
+ settingsBundle.putBoolean(
+ ContentResolver.SYNC_EXTRAS_EXPEDITED, true);
+ /*
+ * Request the sync for the default account, authority, and
+ * manual sync settings
+ */
+ ContentResolver.requestSync(mAccount, AUTHORITY, settingsBundle);
+ }
+
diff --git a/docs/html/training/training_toc.cs b/docs/html/training/training_toc.cs
index 58db404e8133d..cb5775288b9b9 100644
--- a/docs/html/training/training_toc.cs
+++ b/docs/html/training/training_toc.cs
@@ -475,6 +475,37 @@
+