diff options
-rw-r--r-- | docs/downloads/training/BasicSyncAdapter.zip | bin | 0 -> 2347174 bytes | |||
-rw-r--r-- | docs/html/training/sync-adapters/creating-authenticator.jd | 290 | ||||
-rw-r--r-- | docs/html/training/sync-adapters/creating-stub-provider.jd | 203 | ||||
-rw-r--r-- | docs/html/training/sync-adapters/creating-sync-adapter.jd | 658 | ||||
-rw-r--r-- | docs/html/training/sync-adapters/index.jd | 135 | ||||
-rw-r--r-- | docs/html/training/sync-adapters/running-sync-adapter.jd | 524 | ||||
-rw-r--r-- | docs/html/training/training_toc.cs | 31 |
7 files changed, 1841 insertions, 0 deletions
diff --git a/docs/downloads/training/BasicSyncAdapter.zip b/docs/downloads/training/BasicSyncAdapter.zip Binary files differnew file mode 100644 index 000000000000..25aa8bb53d59 --- /dev/null +++ b/docs/downloads/training/BasicSyncAdapter.zip 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 000000000000..1b272e7b5994 --- /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 + + +<div id="tb-wrapper"> +<div id="tb"> + +<h2>This lesson teaches you to</h2> +<ol> + <li> + <a href="#CreateAuthenticator">Add a Stub Authenticator Component</a> + </li> + <li> + <a href="#CreateAuthenticatorService">Bind the Authenticator to the Framework</a> + </li> + <li> + <a href="#CreateAuthenticatorFile">Add the Authenticator Metadata File</a> + </li> + <li> + <a href="#DeclareAuthenticator">Declare the Authenticator in the Manifest</a> + </li> +</ol> + +<h2>You should also read</h2> +<ul> + <li> + <a href="{@docRoot}guide/components/bound-services.html">Bound Services</a> + </li> +</ul> + +<h2>Try it out</h2> + +<div class="download-box"> + <a href="http://developer.android.com/shareables/training/BasicSyncAdapter.zip" class="button">Download the sample</a> + <p class="filename">BasicSyncAdapter.zip</p> +</div> + +</div> +</div> +<p> + 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. +</p> +<p> + 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. +</p> +<p> + 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}. +</p> + +<h2 id="CreateAuthenticator">Add a Stub Authenticator Component</h2> +<p> + 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. +</p> +<p> + The following snippet shows an example of a stub authenticator class: +</p> +<pre> +/* + * 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(); + } +} +</pre> +<h2 id="CreateAuthenticatorService">Bind the Authenticator to the Framework</h2> +<p> + 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. +</p> +<p> + 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. +</p> +<p> + The following snippet shows you how to define the bound {@link android.app.Service}: +</p> +<pre> +/** + * 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(); + } +} +</pre> + +<h2 id="CreateAuthenticatorFile">Add the Authenticator Metadata File</h2> +<p> + 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}. +</p> +<p> + This XML file contains a single element <code><account-authenticator></code> that + has the following attributes: +</p> +<dl> + <dt> + <code>android:accountType</code> + </dt> + <dd> + The sync adapter framework requires each sync adapter to have an account type, in the form + of a domain name. The framework uses the account type as part of the sync adapter's + internal identification. For servers that require login, the account type along with a + user account is sent to the server as part of the login credentials. + <p> + 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. + </p> + </dd> + <dt> + <code>android:icon</code> + </dt> + <dd> + Pointer to a <a href="{@docRoot}guide/topics/resources/drawable-resource.html">Drawable</a> + resource containing an icon. If you make the sync adapter visible by specifying the + attribute <code>android:userVisible="true"</code> in <code>res/xml/syncadapter.xml</code>, + then you must provide this icon resource. It appears in the <b>Accounts</b> section of + the system's Settings app. + </dd> + <dt> + <code>android:smallIcon</code> + </dt> + <dd> + Pointer to a <a href="{@docRoot}guide/topics/resources/drawable-resource.html">Drawable</a> + resource containing a small version of the icon. This resource may be used instead of + <code>android:icon</code> in the <b>Accounts</b> section of the system's Settings app, + depending on the screen size. + </dd> + <dt> + <code>android:label</code> + </dt> + <dd> + Localizable string that identifies the account type to users. If you make the sync adapter + visible by specifying the attribute <code>android:userVisible="true"</code> in + <code>res/xml/syncadapter.xml</code>, then you should provide this string. It appears in the + <b>Accounts</b> section of the system's Settings app, next to the icon you define for the + authenticator. + </dd> +</dl> +<p> + The following snippet shows the XML file for the authenticator you created previously: +</p> +<pre>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"/> +</pre> + +<h2 id="DeclareAuthenticator">Declare the Authenticator in the Manifest</h2> +<p> + 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 + <code><a href="{@docRoot}guide/topics/manifest/service-element.html"><service></a></code> + element as a child element of +<code><a href="{@docRoot}guide/topics/manifest/application-element.html"><application></a></code>: +</p> +<pre> + <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> +</pre> +<p> + The +<code><a href="{@docRoot}guide/topics/manifest/intent-filter-element.html"><intent-filter></a></code> + 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. +</p> +<p> + The +<code><a href="{@docRoot}guide/topics/manifest/meta-data-element.html"><meta-data></a></code> + element declares the metadata for the authenticator. The +<code><a href="{@docRoot}guide/topics/manifest/meta-data-element.html#nm">android:name</a></code> + attribute links the meta-data to the authentication framework. The +<code><a href="{@docRoot}guide/topics/manifest/meta-data-element.html#rsrc">android:resource</a></code> + element specifies the name of the authenticator metadata file you created previously. +</p> +<p> + 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 <a href="creating-sync-adapter.html" + >Creating a Sync Adapter</a>. +</p> 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 000000000000..8f6eba037ef7 --- /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 + + +<div id="tb-wrapper"> +<div id="tb"> + +<h2>This lesson teaches you to</h2> +<ol> + <li> + <a href="#CreateProvider">Add a Stub Content Provider</a> + </li> + <li> + <a href="#DeclareProvider">Declare the Provider in the Manifest</a> + </li> +</ol> + +<h2>You should also read</h2> +<ul> + <li> + <a href="{@docRoot}guide/topics/providers/content-provider-basics.html" + >Content Provider Basics</a> + </li> +</ul> + +<h2>Try it out</h2> + +<div class="download-box"> + <a href="http://developer.android.com/shareables/training/BasicSyncAdapter.zip" class="button">Download the sample</a> + <p class="filename">BasicSyncAdapter.zip</p> +</div> + +</div> +</div> +<p> + 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. +</p> +<p> + 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 <a href="{@docRoot}guide/topics/providers/content-provider-creating.html" + >Creating a Content Provider</a>. +</p> +<p> + 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. +</p> +<p> + 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 + <a href="creating-sync-adapter.html">Creating a Sync Adapter</a>. 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. +</p> +<h2 id="CreateProvider">Add a Stub Content Provider</h2> +<p> + 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: +</p> +<pre> +/* + * 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; + } +} +</pre> +<h2 id="DeclareProvider">Declare the Provider in the Manifest</h2> +<p> + 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 <code><a href="{@docRoot}guide/topics/manifest/provider-element.html" + ><provider></a></code> element with the following attributes: +</p> +<dl> + <dt> + <code>android:name="com.example.android.datasync.provider.StubProvider"</code> + </dt> + <dd> + Specifies the fully-qualified name of the class that implements the stub content provider. + </dd> + <dt> + <code>android:authorities="com.example.android.datasync.provider"</code> + </dt> + <dd> + A URI authority that identifies the stub content provider. Make this value your app's + package name with the string ".provider" appended to it. Even though you're declaring your + stub provider to the system, nothing tries to access the provider itself. + </dd> + <dt> + <code>android:exported="false"</code> + </dt> + <dd> + Determines whether other apps can access the content provider. For your stub content + provider, set the value to {@code false}, since there's no need to allow other apps to see + the provider. This value doesn't affect the interaction between the sync adapter framework + and the content provider. + </dd> + <dt> + <code>android:syncable="true"</code> + </dt> + <dd> + Sets a flag that indicates that the provider is syncable. If you set this flag to + {@code true}, you don't have to call {@link android.content.ContentResolver#setIsSyncable + setIsSyncable()} in your code. The flag allows the sync adapter framework to make data + transfers with the content provider, but transfers only occur if you do them explicitly. + </dd> +</dl> +<p> + The following snippet shows you how to add the + <code><a href="{@docRoot}guide/topics/manifest/provider-element.html" + ><provider></a></code> element to the app manifest: +</p> +<pre> +<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> +</pre> +<p> + 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. +</p> 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 000000000000..7c59c8c69703 --- /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 + +<div id="tb-wrapper"> +<div id="tb"> + +<h2>This lesson teaches you to</h2> +<ol> + <li> + <a href="#CreateSyncAdapter" + >Create the Sync Adapter Class</a> + </li> + <li> + <a href="#CreateSyncAdapterService">Bind the Sync Adapter to the Framework</a> + </li> + <li> + <a href="#CreateAccountTypeAccount" + >Add the Account Required by the Framework</a> + </li> + <li> + <a href="#CreateSyncAdapterMetadata">Add the Sync Adapter Metadata File</a> + </li> + <li> + <a href="#DeclareSyncAdapterManifest">Declare the Sync Adapter in the Manifest</a> + </li> +</ol> + +<h2>You should also read</h2> +<ul> + <li> + <a href="{@docRoot}guide/components/bound-services.html">Bound Services</a> + </li> + <li> + <a href="{@docRoot}guide/topics/providers/content-providers.html">Content Providers</a> + </li> + <li> + <a href="{@docRoot}training/id-auth/custom_auth.html">Creating a Custom Account Type</a> + </li> +</ul> + +<h2>Try it out</h2> + +<div class="download-box"> + <a href="http://developer.android.com/shareables/training/BasicSyncAdapter.zip" class="button">Download the sample</a> + <p class="filename">BasicSyncAdapter.zip</p> +</div> + +</div> +</div> +<p> + 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: +<dl> + <dt> + Sync adapter class. + </dt> + <dd> + A class that wraps your data transfer code in an interface compatible with the sync adapter + framework. + </dd> + <dt> + Bound {@link android.app.Service}. + </dt> + <dd> + A component that allows the sync adapter framework to run the code in your sync adapter + class. + </dd> + <dt> + Sync adapter XML metadata file. + </dt> + <dd> + A file containing information about your sync adapter. The framework reads this file to + find out how to load and schedule your data transfer. + </dd> + <dt> + Declarations in the app manifest. + </dt> + <dd> + XML that declares the bound service and points to sync adapter-specific metadata. + </dd> +</dl> +<p> + This lesson shows you how to define these elements. +</p> +<h2 id="CreateSyncAdapter">Create a Sync Adapter Class</h2> +<p> + 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. +</p> +<h3>Extend the base sync adapter class AbstractThreadedSyncAdapter</h3> +<p> + 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. +</p> +<p class="note"> + <strong>Note:</strong> 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 + <a href="#CreateSyncAdapterService">Bind the Sync Adapter to the Framework</a>. +</p> +<p> + The following example shows you how to implement + {@link android.content.AbstractThreadedSyncAdapter}and its constructors: +</p> +<pre style="clear: right"> +/** + * 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(); + ... + } +</pre> +<h3>Add the data transfer code to onPerformSync()</h3> +<p> + 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()}. +</p> +<p> + 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: +</p> +<dl> + <dt> + Account + </dt> + <dd> + An {@link android.accounts.Account} object associated with the event that triggered + the sync adapter. If your server doesn't use accounts, you don't need to use the + information in this object. + </dd> + <dt> + Extras + </dt> + <dd> + A {@link android.os.Bundle} containing flags sent by the event that triggered the sync + adapter. + </dd> + <dt> + Authority + </dt> + <dd> + The authority of a content provider in the system. Your app has to have access to + this provider. Usually, the authority corresponds to a content provider in your own app. + </dd> + <dt> + Content provider client + </dt> + <dd> + A {@link android.content.ContentProviderClient} for the content provider pointed to by the + authority argument. A {@link android.content.ContentProviderClient} is a lightweight public + interface to a content provider. It has the same basic functionality as a + {@link android.content.ContentResolver}. If you're using a content provider to store data + for your app, you can connect to the provider with this object. Otherwise, you can ignore + it. + </dd> + <dt> + Sync result + </dt> + <dd> + A {@link android.content.SyncResult} object that you use to send information to the sync + adapter framework. + </dd> +</dl> +<p> + The following snippet shows the overall structure of + {@link android.content.AbstractThreadedSyncAdapter#onPerformSync onPerformSync()}: +</p> +<pre> + /* + * 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. + */ + ... + } +</pre> +<p> + 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: +</p> +<dl> + <dt> + Connecting to a server + </dt> + <dd> + Although you can assume that the network is available when your data transfer starts, the + sync adapter framework doesn't automatically connect to a server. + </dd> + <dt> + Downloading and uploading data + </dt> + <dd> + A sync adapter doesn't automate any data transfer tasks. If you want to download + data from a server and store it in a content provider, you have to provide the code that + requests the data, downloads it, and inserts it in the provider. Similarly, if you want to + send data to a server, you have to read it from a file, database, or provider, and send + the necessary upload request. You also have to handle network errors that occur while your + data transfer is running. + </dd> + <dt> + Handling data conflicts or determining how current the data is + </dt> + <dd> + A sync adapter doesn't automatically handle conflicts between data on the server and data + on the device. Also, it doesn't automatically detect if the data on the server is newer than + the data on the device, or vice versa. Instead, you have to provide your own algorithms for + handling this situation. + </dd> + <dt> + Clean up. + </dt> + <dd> + Always close connections to a server and clean up temp files and caches at the end of + your data transfer. + </dd> +</dl> +<p class="note"> + <strong>Note:</strong> 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. +</p> +<p> + 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 <a href="{@docRoot}training/efficient-downloads/index.html" + >Transferring Data Without Draining the Battery</a>, which describes several network access + tasks you can include in your data transfer code. +</p> +<h2 id="CreateSyncAdapterService">Bind the Sync Adapter to the Framework</h2> +<p> + 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. +</p> +<p> + 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. +</p> +<p> + 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: +</p> +<pre> +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(); + } +} +</pre> +<p class="note"> + <strong>Note:</strong> To see a more detailed example of a bound service for a sync adapter, + see the sample app. +</p> +<h2 id="CreateAccountTypeAccount">Add the Account Required by the Framework</h2> +<p> + The sync adapter framework requires each sync adapter to have an account type. You declared + the account type value in the section + <a href="creating-authenticator.html#CreateAuthenticatorFile" + >Add the Authenticator Metadata File</a>. 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()}. +</p> +<p> + 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: +</p> +<pre> +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. + */ + } + } + ... +} +</pre> +<h2 id="CreateSyncAdapterMetadata">Add the Sync Adapter Metadata File</h2> +<p> + 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}. +</p> +<p> + This XML file contains a single XML element <code><sync-adapter></code> that has the + following attributes: +</p> +<dl> + <dt><code>android:contentAuthority</code></dt> + <dd> + The URI authority for your content provider. If you created a stub content provider for + your app in the previous lesson <a href="creating-stub-provider.html" + >Creating a Stub Content Provider</a>, use the value you specified for the + attribute +<code><a href="{@docRoot}guide/topics/manifest/provider-element.html#auth">android:authorities</a></code> + in the <code><a href="{@docRoot}guide/topics/manifest/provider-element.html" + ><provider></a></code> element you added to your app manifest. This attribute is + described in more detail in the section + <a href="creating-stub-provider.html#DeclareProvider" + >Declare the Provider in the Manifest</a>. + <br/> + If you're transferring data from a content provider to a server with your sync adapter, this + value should be the same as the content URI authority you're using for that data. This value + is also one of the authorities you specify in the +<code><a href="{@docRoot}guide/topics/manifest/provider-element.html#auth">android:authorities</a></code> + attribute of the <code><a href="{@docRoot}guide/topics/manifest/provider-element.html" + ><provider></a></code> element that declares your provider in your app manifest. + </dd> + <dt><code>android:accountType</code></dt> + <dd> + The account type required by the sync adapter framework. The value must be the same + as the account type value you provided when you created the authenticator metadata file, as + described in the section <a href="creating-authenticator.html#CreateAuthenticatorFile" + >Add the Authenticator Metadata File</a>. It's also the value you specified for the + constant {@code ACCOUNT_TYPE} in the code snippet in the section + <a href="#CreateAccountTypeAccount">Add the Account Required by the Framework</a>. + </dd> + <dt>Settings attributes</dt> + <dd> + <dl> + <dt> + {@code android:userVisible} + </dt> + <dd> + Sets the visibility of the sync adapter's account type. By default, the + account icon and label associated with the account type are visible in the + <b>Accounts</b> section of the system's Settings app, so you should make your sync + adapter invisible unless you have an account type or domain that's easily associated + with your app. If you make your account type invisible, you can still allow users to + control your sync adapter with a user interface in one of your app's activities. + </dd> + <dt> + {@code android:supportsUploading} + </dt> + <dd> + Allows you to upload data to the cloud. Set this to {@code false} if your app only + downloads data. + </dd> + <dt> + {@code android:allowParallelSyncs} + </dt> + <dd> + Allows multiple instances of your sync adapter component to run at the same time. + Use this if your app supports multiple user accounts and you want to allow multiple + users to transfer data in parallel. This flag has no effect if you never run + multiple data transfers. + </dd> + <dt> + {@code android:isAlwaysSyncable} + </dt> + <dd> + Indicates to the sync adapter framework that it can run your sync adapter at any + time you've specified. If you want to programmatically control when your sync + adapter can run, set this flag to {@code false}, and then call + {@link android.content.ContentResolver#requestSync requestSync()} to run the + sync adapter. To learn more about running a sync adapter, see the lesson + <a href="running-sync-adapter.html">Running a Sync Adapter</a> + </dd> + </dl> + </dd> +</dl> +<p> + The following example shows the XML for a sync adapter that uses a single dummy account and + only does downloads. +</p> +<pre> +<?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"/> +</pre> + +<h2 id="DeclareSyncAdapterManifest">Declare the Sync Adapter in the Manifest</h2> +<p> + 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. +</p> +<p> + 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 <a href="creating-authenticator.html">Creating a Stub Authenticator</a>. +</p> +<p> + To request these permissions, add the following to your app manifest as child elements of +<code><a href="{@docRoot}guide/topics/manifest/manifest-element.html"><manifest></a></code>: +</p> +<dl> + <dt> + {@link android.Manifest.permission#INTERNET android.permission.INTERNET} + </dt> + <dd> + Allows the sync adapter code to access the Internet so that it can download or upload data + from the device to a server. You don't need to add this permission again if you were + requesting it previously. + </dd> + <dt> +{@link android.Manifest.permission#READ_SYNC_SETTINGS android.permission.READ_SYNC_SETTINGS} + </dt> + <dd> + Allows your app to read the current sync adapter settings. For example, you need this + permission in order to call {@link android.content.ContentResolver#getIsSyncable + getIsSyncable()}. + </dd> + <dt> +{@link android.Manifest.permission#WRITE_SYNC_SETTINGS android.permission.WRITE_SYNC_SETTINGS} + </dt> + <dd> + Allows your app to control sync adapter settings. You need this permission in order to + set periodic sync adapter runs using {@link android.content.ContentResolver#addPeriodicSync + addPeriodicSync()}. This permission is <b>not</b> required to call + {@link android.content.ContentResolver#requestSync requestSync()}. To learn more about + running the sync adapter, see <a href="running-sync-adapter.html" + >Running A Sync Adapter</a>. + </dd> + <dt> +{@link android.Manifest.permission#AUTHENTICATE_ACCOUNTS android.permission.AUTHENTICATE_ACCOUNTS} + </dt> + <dd> + Allows you to use the authenticator component you created in the lesson + <a href="creating-authenticator.html">Creating a Stub Authenticator</a>. + </dd> +</dl> +<p> + The following snippet shows how to add the permissions: +</p> +<pre> +<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> +</pre> +<p> + 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 <code><a href="{@docRoot}guide/topics/manifest/application-element.html" + ><application></a></code>: +</p> +<pre> + <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> +</pre> +<p> + The +<code><a href="{@docRoot}guide/topics/manifest/intent-filter-element.html"><intent-filter></a></code> + 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 +<code><a href="{@docRoot}guide/topics/manifest/service-element.html#exported">android:exported="true"</a></code> + allows processes other than your app (including the system) to access the + {@link android.app.Service}. The attribute +<code><a href="{@docRoot}guide/topics/manifest/service-element.html#proc">android:process=":sync"</a></code> + 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. +</p> +<p> + The +<code><a href="{@docRoot}guide/topics/manifest/meta-data-element.html"><meta-data></a></code> + element provides provides the name of the sync adapter metadata XML file you created previously. + The +<code><a href="{@docRoot}guide/topics/manifest/meta-data-element.html#nm">android:name</a></code> + attribute indicates that this metadata is for the sync adapter framework. The +<code><a href="{@docRoot}guide/topics/manifest/meta-data-element.html#rsrc">android:resource</a></code> + element specifies the name of the metadata file. +</p> +<p> + 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. +</p> diff --git a/docs/html/training/sync-adapters/index.jd b/docs/html/training/sync-adapters/index.jd new file mode 100644 index 000000000000..1f7977b2d4fc --- /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 + +<div id="tb-wrapper"> +<div id="tb"> + +<h2>Dependencies and prerequisites</h2> +<ul> + <li>Android 3.0 (API Level 11) or higher</li> +</ul> + +<h2>You should also read</h2> +<ul> + <li> + <a href="{@docRoot}guide/components/bound-services.html">Bound Services</a> + </li> + <li> + <a href="{@docRoot}guide/topics/providers/content-providers.html">Content Providers</a> + </li> + <li> + <a href="{@docRoot}training/id-auth/custom_auth.html">Creating a Custom Account Type</a> + </li> +</ul> + +<h2>Try it out</h2> + +<div class="download-box"> + <a href="http://developer.android.com/shareables/training/BasicSyncAdapter.zip" class="button">Download the sample</a> + <p class="filename">BasicSyncAdapter.zip</p> +</div> + +</div> +</div> +<p> + 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. +</p> +<p> + 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: +</p> +<dl> + <dt> + Plug-in architecture + </dt> + <dd> + Allows you to add data transfer code to the system in the form of callable components. + </dd> + <dt> + Automated execution + </dt> + <dd> + Allows you to automate data transfer based on a variety of criteria, including data changes, + elapsed time, or time of day. In addition, the system adds transfers that are unable to + run to a queue, and runs them when possible. + </dd> + <dt> + Automated network checking + </dt> + <dd> + The system only runs your data transfer when the device has network connectivity. + </dd> + <dt> + Improved battery performance + </dt> + <dd> + Allows you to centralize all of your app's data transfer tasks in one place, so that they + all run at the same time. Your data transfer is also scheduled in conjunction with data + transfers from other apps. These factors reduce the number of times the system has to + switch on the network, which reduces battery usage. + </dd> + <dt> + Account management and authentication + </dt> + <dd> + If your app requires user credentials or server login, you can optionally + integrate account management and authentication into your data transfer. + </dd> +</dl> +<p> + 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. +</p> +<p class="note"> + <strong>Note:</strong> 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}. +</p> +<h2>Lessons</h2> +<dl> + <dt> + <b><a href="creating-authenticator.html">Creating a Stub Authenticator</a></b> + </dt> + <dd> + Learn how to add an account-handling component that the sync adapter framework expects to be + part of your app. This lesson shows you how to create a stub authentication component for + simplicity. + </dd> + <dt> + <b><a href="creating-stub-provider.html">Creating a Stub Content Provider</a></b> + </dt> + <dd> + Learn how to add a content provider component that the sync adapter framework expects to be + part of your app. This lesson assumes that your app doesn't use a content provider, so it + shows you how to add a stub component. If you have a content provider already in your app, + you can skip this lesson. + </dd> + <dt> + <b><a href="creating-sync-adapter.html">Creating a Sync Adapter</a></b> + </dt> + <dd> + Learn how to encapsulate your data transfer code in a component that the sync + adapter framework can run automatically. + </dd> + <dt> + <b><a href="running-sync-adapter.html">Running a Sync Adapter</a></b> + </dt> + <dd> + Learn how to trigger and schedule data transfers using the sync adapter framework. + </dd> +</dl> diff --git a/docs/html/training/sync-adapters/running-sync-adapter.jd b/docs/html/training/sync-adapters/running-sync-adapter.jd new file mode 100644 index 000000000000..8fb7e80ce5bd --- /dev/null +++ b/docs/html/training/sync-adapters/running-sync-adapter.jd @@ -0,0 +1,524 @@ +page.title=Running a Sync Adapter + +trainingnavtop=true +@jd:body + + +<div id="tb-wrapper"> +<div id="tb"> + +<h2>This lesson teaches you how to:</h2> +<ol> + <li><a href="#RunByMessage">Run the Sync Adapter When Server Data Changes</a> + <li><a href="#RunDataChange">Run the Sync Adapter When Content Provider Data Changes</a></li> + <li><a href="#RunByNetwork">Run the Sync Adapter After a Network Message</a></li> + <li><a href="#RunPeriodic">Run the Sync Adapter Periodically</a></li> + <li><a href="#RunOnDemand">Run the Sync Adapter On Demand</a></li> +</ol> + + +<h2>You should also read</h2> +<ul> + <li> + <a href="{@docRoot}guide/topics/providers/content-providers.html">Content Providers</a> + </li> +</ul> + +<h2>Try it out</h2> + +<div class="download-box"> + <a href="http://developer.android.com/shareables/training/BasicSyncAdapter.zip" class="button">Download the sample</a> + <p class="filename">BasicSyncAdapter.zip</p> +</div> + +</div> +</div> +<p> + 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. +</p> +<p> + 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. +</p> +<p> + You have the following options for running your sync adapter: +</p> +<dl> + <dt> + When server data changes + </dt> + <dd> + Run the sync adapter in response to a message from a server, indicating that server-based + data has changed. This option allows you to refresh data from the server to the device + without degrading performance or wasting battery life by polling the server. + </dd> + <dt>When device data changes</dt> + <dd> + Run a sync adapter when data changes on the device. This option allows you to send + modified data from the device to a server, and is especially useful if you need to ensure + that the server always has the latest device data. This option is straightforward to + implement if you actually store data in your content provider. If you're using a stub + content provider, detecting data changes may be more difficult. + </dd> + <dt> + When the system sends out a network message + </dt> + <dd> + Run a sync adapter when the Android system sends out a network message that keeps the + TCP/IP connection open; this message is a basic part of the networking framework. Using + this option is one way to run the sync adapter automatically. Consider using it in + conjunction with interval-based sync adapter runs. + </dd> + <dt> + At regular intervals + </dt> + <dd> + Run a sync adapter after the expiration of an interval you choose, or run it at a certain + time every day. + </dd> + <dt>On demand</dt> + <dd> + Run the sync adapter in response to a user action. However, to provide the best user + experience you should rely primarily on one of the more automated options. By using + automated options, you conserve battery and network resources. + </dd> +</dl> +<p> + The rest of this lesson describes each of the options in more detail. +</p> +<h2 id="RunByMessage">Run the Sync Adapter When Server Data Changes</h2> +<p> + 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. +</p> +<p> + <a href="{@docRoot}google/gcm/index.html">Google Cloud Messaging</a> (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. +</p> +<p class="note"> + <strong>Note:</strong> 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. +<p> + The following code snippet shows you how to run + {@link android.content.ContentResolver#requestSync requestSync()} in response to an + incoming GCM message: +</p> +<pre> +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); + ... + } + ... + } + ... +} +</pre> +<h2 id="RunDataChange">Run the Sync Adapter When Content Provider Data Changes</h2> +<p> + 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. +</p> +<p class="note"> + <strong>Note:</strong> 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. +</p> +<p> + 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. +</p> +<p> + 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. +</p> + +<p> + 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: +</p> +<pre> +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); + ... + } + ... +} +</pre> +<h2 id="RunByNetwork">Run the Sync Adapter After a Network Message</h2> +<p> + 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. +</p> +<p> + 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. +</p> +<p> + 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()}. +</p> +<p> + 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: +</p> +<pre> +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); + ... + } + ... +} +</pre> +<h2 id="RunPeriodic">Run the Sync Adapter Periodically</h2> +<p> + 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. +</p> +<p> + 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. +</p> +<p> + 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. +</p> +<p> + 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. +</p> +<p> + 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. +</p> +<p> + 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()}. +</p> +<p> + The following code snippet shows you how to schedule periodic sync adapter runs: +</p> +<pre> +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); + ... + } + ... +} +</pre> +<h2 id="RunOnDemand">Run the Sync Adapter On Demand</h2> +<p> + 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. +</p> +<p> + 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. +</p> +<p> + 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()}. +</p> +<p> + Run on demand transfers with the following flags: +</p> +<dl> + <dt> + {@link android.content.ContentResolver#SYNC_EXTRAS_MANUAL SYNC_EXTRAS_MANUAL} + </dt> + <dd> + Forces a manual sync. The sync adapter framework ignores the existing settings, + such as the flag set by {@link android.content.ContentResolver#setSyncAutomatically + setSyncAutomatically()}. + </dd> + <dt> + {@link android.content.ContentResolver#SYNC_EXTRAS_EXPEDITED SYNC_EXTRAS_EXPEDITED} + </dt> + <dd> + Forces the sync to start immediately. If you don't set this, the system may wait several + seconds before running the sync request, because it tries to optimize battery use by + scheduling many requests in a short period of time. + </dd> +</dl> +<p> + The following code snippet shows you how to call + {@link android.content.ContentResolver#requestSync requestSync()} in response to a button + click: +</p> +<pre> +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); + } +</pre> diff --git a/docs/html/training/training_toc.cs b/docs/html/training/training_toc.cs index 58db404e8133..cb5775288b9b 100644 --- a/docs/html/training/training_toc.cs +++ b/docs/html/training/training_toc.cs @@ -475,6 +475,37 @@ </a> </li> </li> + <li class="nav-section"> + <div class="nav-section-header"> + <a href="<?cs var:toroot ?>training/sync-adapters/index.html" + description="How to transfer data between the cloud and the device using the Android + sync adapter framework" + >Transferring Data Using Sync Adapters</a> + </div> + <ul> + <li> + <a href="<?cs var:toroot ?>training/sync-adapters/creating-authenticator.html"> + Creating a Stub Authenticator + </a> + </li> + <li> + <a href="<?cs var:toroot ?>training/sync-adapters/creating-stub-provider.html"> + Creating a Stub Content Provider + </a> + </li> + <li> + <a href="<?cs var:toroot ?>training/sync-adapters/creating-sync-adapter.html"> + Creating a Sync Adapter + </a> + </li> + <li> + <a href="<?cs var:toroot ?>training/sync-adapters/running-sync-adapter.html"> + Running a Sync Adapter + </a> + </li> + </ul> + </li> + </ul> </li> <!-- End connectivity and cloud --> |