am 8b3c7d0c: am c355a1c1: DOC: Sync Adapters Android Training

* commit '8b3c7d0c8fec82a6ad32abcb7e78416d8513512d':
  DOC: Sync Adapters Android Training
This commit is contained in:
Joe Malin
2013-07-08 10:19:25 -07:00
committed by Android Git Automerger
7 changed files with 1841 additions and 0 deletions

Binary file not shown.

View File

@@ -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
&#64;Override
public Bundle editProperties(
AccountAuthenticatorResponse r, String s) {
throw new UnsupportedOperationException();
}
// Don't add additional accounts
&#64;Override
public Bundle addAccount(
AccountAuthenticatorResponse r,
String s,
String s2,
String[] strings,
Bundle bundle) throws NetworkErrorException {
return null;
}
// Ignore attempts to confirm credentials
&#64;Override
public Bundle confirmCredentials(
AccountAuthenticatorResponse r,
Account account,
Bundle bundle) throws NetworkErrorException {
return null;
}
// Getting an authentication token is not supported
&#64;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
&#64;Override
public String getAuthTokenLabel(String s) {
throw new UnsupportedOperationException();
}
// Updating user credentials is not supported
&#64;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
&#64;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;
&#64;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.
*/
&#64;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>&lt;account-authenticator&gt;</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
&lt;?xml version="1.0" encoding="utf-8"?&gt;
&lt;account-authenticator
xmlns:android="http://schemas.android.com/apk/res/android"
android:accountType="example.com"
android:icon="&#64;drawable/ic_launcher"
android:smallIcon="&#64;drawable/ic_launcher"
android:label="&#64;string/app_name"/&gt;
</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">&lt;service&gt;</a></code>
element as a child element of
<code><a href="{@docRoot}guide/topics/manifest/application-element.html">&lt;application&gt;</a></code>:
</p>
<pre>
&lt;service
android:name="com.example.android.syncadapter.AuthenticatorService"&gt;
&lt;intent-filter&gt;
&lt;action android:name="android.accounts.AccountAuthenticator"/&gt;
&lt;/intent-filter&gt;
&lt;meta-data
android:name="android.accounts.AccountAuthenticator"
android:resource="@xml/authenticator" /&gt;
&lt;/service&gt;
</pre>
<p>
The
<code><a href="{@docRoot}guide/topics/manifest/intent-filter-element.html">&lt;intent-filter&gt;</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">&lt;meta-data&gt;</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>

View File

@@ -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.
*/
&#64;Override
public boolean onCreate() {
return true;
}
/*
* Return an empty String for MIME type
*/
&#64;Override
public String getType() {
return new String();
}
/*
* query() always returns no results
*
*/
&#64;Override
public Cursor query(
Uri uri,
String[] projection,
String selection,
String[] selectionArgs,
String sortOrder) {
return null;
}
/*
* insert() always returns null (no URI)
*/
&#64;Override
public Uri insert(Uri uri, ContentValues values) {
return null;
}
/*
* delete() always returns "no rows affected" (0)
*/
&#64;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"
>&lt;provider&gt;</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"
>&lt;provider&gt;</a></code> element to the app manifest:
</p>
<pre>
&lt;manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.android.network.sync.BasicSyncAdapter"
android:versionCode="1"
android:versionName="1.0" &gt;
&lt;application
android:allowBackup="true"
android:icon="&#64;drawable/ic_launcher"
android:label="&#64;string/app_name"
android:theme="&#64;style/AppTheme" &gt;
...
&lt;provider
android:name="com.example.android.datasync.provider.StubProvider"
android:authorities="com.example.android.datasync.provider"
android:export="false"
android:syncable="true"/&gt;
...
&lt;/application&gt;
&lt;/manifest&gt;
</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>

View File

@@ -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.
*/
&#64;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.
*/
&#64;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.
*
*/
&#64;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;
...
&#64;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 &lt;provider&gt; 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>&lt;sync-adapter&gt;</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"
>&lt;provider&gt;</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"
>&lt;provider&gt;</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>
&lt;?xml version="1.0" encoding="utf-8"?&gt;
&lt;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"/&gt;
</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">&lt;manifest&gt;</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>
&lt;manifest&gt;
...
&lt;uses-permission
android:name="android.permission.INTERNET"/&gt;
&lt;uses-permission
android:name="android.permission.READ_SYNC_SETTINGS"/&gt;
&lt;uses-permission
android:name="android.permission.WRITE_SYNC_SETTINGS"/&gt;
&lt;uses-permission
android:name="android.permission.AUTHENTICATE_ACCOUNTS"/&gt;
...
&lt;/manifest&gt;
</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"
>&lt;application&gt;</a></code>:
</p>
<pre>
&lt;service
android:name="com.example.android.datasync.SyncService"
android:exported="true"
android:process=":sync"&gt;
&lt;intent-filter&gt;com.example.android.datasync.provider
&lt;action android:name="android.content.SyncAdapter"/&gt;
&lt;/intent-filter&gt;
&lt;meta-data android:name="android.content.SyncAdapter"
android:resource="&#64;xml/syncadapter" /&gt;
&lt;/service&gt;
</pre>
<p>
The
<code><a href="{@docRoot}guide/topics/manifest/intent-filter-element.html">&lt;intent-filter&gt;</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">&lt;meta-data&gt;</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>

View File

@@ -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>

View File

@@ -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";
...
&#64;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)
&amp;&amp;
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.
*/
&#64;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.
*/
&#64;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);
}
...
}
...
&#64;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;
...
&#64;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;
...
&#64;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;
...
&#64;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>

View File

@@ -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 -->