Android开发训练之第五章第六节——Transferring Data Using Sync Adapters

时间:2022-05-01 22:05:07

Transferring Data Using Sync Adapters

DEPENDENCIES AND PREREQUISITES

  • Android 2.1 (API Level 7) or higher

YOU SHOULD ALSO READ

TRY IT OUT

Download the sample

BasicSyncAdapter.zip

Synchronizing data between an Android device and web servers can make your application significantly more useful and compelling for your users. For example, transferring data to a web server makes a useful backup, and transferring data from a server makes it available to the user even when the device is offline. In some cases, users may find it easier to enter and edit their data in a web interface and then have that data available on their device, or they may want to collect data over time and then upload it to a central storage area.

Although you can design your own system for doing data transfers in your app, you should consider using Android's sync adapter framework. This framework helps manage and automate data transfers, and coordinates synchronization operations across different apps. When you use this framework, you can take advantage of several features that aren't available to data transfer schemes you design yourself:

Plug-in architecture
Allows you to add data transfer code to the system in the form of callable components.
Automated execution
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.
Automated network checking
The system only runs your data transfer when the device has network connectivity.
Improved battery performance
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.
Account management and authentication
If your app requires user credentials or server login, you can optionally integrate account management and authentication into your data transfer.

This class shows you how to create a sync adapter and the bound Service that wraps it, how to provide the other components that help you plug the sync adapter into the framework, and how to run the sync adapter to run in various ways.

Note: Sync adapters run asynchronously, so you should use them with the expectation that they transfer data regularly and efficiently, but not instantaneously. If you need to do real-time data transfer, you should do it in an AsyncTask or an IntentService.

Lessons


Creating a Stub Authenticator
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.
Creating a Stub Content Provider
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.
Creating a Sync Adapter
Learn how to encapsulate your data transfer code in a component that the sync adapter framework can run automatically.
Running a Sync Adapter
Learn how to trigger and schedule data transfers using the sync adapter framework.

Creating a Stub Authenticator

The sync adapter framework assumes that your sync adapter transfers data between device storage associated with an account and server storage that requires login access. For this reason, the framework expects you to provide a component called an authenticator as part of your sync adapter. This component plugs into the Android accounts and authentication framework and provides a standard interface for handling user credentials such as login information.

Even if your app doesn't use accounts, you still need to provide an authenticator component. If you don't use accounts or server login, the information handled by the authenticator is ignored, so you can provide an authenticator component that contains stub method implementations. You also need to provide a boundService that allows the sync adapter framework to call the authenticator's methods.

This lesson shows you how to define all the parts of a stub authenticator that you need to satisfy the requirements of the sync adapter framework. If you need to provide a real authenticator that handles user accounts, read the reference documentation for AbstractAccountAuthenticator.

Add a Stub Authenticator Component


To add a stub authenticator component to your app, create a class that extends AbstractAccountAuthenticator, and then stub out the required methods, either by returning null or by throwing an exception.

The following snippet shows an example of a stub authenticator class:

/*
 * Implement AbstractAccountAuthenticator and stub out all
 * of its methods
 */
public class Authenticator extends AbstractAccountAuthenticator {
    // Simple constructor
    public Authenticator(Context context) {
        super(context);
    }
    // Editing properties is not supported
    @Override
    public Bundle editProperties(
            AccountAuthenticatorResponse r, String s) {
        throw new UnsupportedOperationException();
    }
    // Don't add additional accounts
    @Override
    public Bundle addAccount(
            AccountAuthenticatorResponse r,
            String s,
            String s2,
            String[] strings,
            Bundle bundle) throws NetworkErrorException {
        return null;
    }
    // Ignore attempts to confirm credentials
    @Override
    public Bundle confirmCredentials(
            AccountAuthenticatorResponse r,
            Account account,
            Bundle bundle) throws NetworkErrorException {
        return null;
    }
    // Getting an authentication token is not supported
    @Override
    public Bundle getAuthToken(
            AccountAuthenticatorResponse r,
            Account account,
            String s,
            Bundle bundle) throws NetworkErrorException {
        throw new UnsupportedOperationException();
    }
    // Getting a label for the auth token is not supported
    @Override
    public String getAuthTokenLabel(String s) {
        throw new UnsupportedOperationException();
    }
    // Updating user credentials is not supported
    @Override
    public Bundle updateCredentials(
            AccountAuthenticatorResponse r,
            Account account,
            String s, Bundle bundle) throws NetworkErrorException {
        throw new UnsupportedOperationException();
    }
    // Checking features for the account is not supported
    @Override
    public Bundle hasFeatures(
        AccountAuthenticatorResponse r,
        Account account, String[] strings) throws NetworkErrorException {
        throw new UnsupportedOperationException();
    }
}

Bind the Authenticator to the Framework


In order for the sync adapter framework to access your authenticator, you must create a bound Service for it. This service provides an Android binder object that allows the framework to call your authenticator and pass data between the authenticator and the framework.

Since the framework starts this 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 Service.onCreate()method of the service.

The following snippet shows you how to define the bound Service:

/**
 * A bound Service that instantiates the authenticator
 * when started.
 */
public class AuthenticatorService extends Service {
    ...
    // Instance field that stores the authenticator object
    private Authenticator mAuthenticator;
    @Override
    public void onCreate() {
        // Create a new authenticator object
        mAuthenticator = new Authenticator(this);
    }
    /*
     * When the system binds to this Service to make the RPC call
     * return the authenticator's IBinder.
     */
    @Override
    public IBinder onBind(Intent intent) {
        return mAuthenticator.getIBinder();
    }
}

Add the Authenticator Metadata File


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 /res/xml/ directory in your app project. You can give any name to the file, although it's usually called authenticator.xml.

This XML file contains a single element <account-authenticator> that has the following attributes:

android:accountType
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.

If your server doesn't require login, you still have to provide an account type. For the value, use a domain name that you control. While the framework uses it to manage your sync adapter, the value is not sent to your server.

android:icon
Pointer to a Drawable resource containing an icon. If you make the sync adapter visible by specifying the attribute android:userVisible="true" in res/xml/syncadapter.xml, then you must provide this icon resource. It appears in the Accounts section of the system's Settings app.
android:smallIcon
Pointer to a Drawable resource containing a small version of the icon. This resource may be used instead ofandroid:icon in the Accounts section of the system's Settings app, depending on the screen size.
android:label
Localizable string that identifies the account type to users. If you make the sync adapter visible by specifying the attribute android:userVisible="true" in res/xml/syncadapter.xml, then you should provide this string. It appears in the Accounts section of the system's Settings app, next to the icon you define for the authenticator.

The following snippet shows the XML file for the authenticator you created previously:

<?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"/>

Declare the Authenticator in the Manifest


In a previous step, you created a bound Service that links the authenticator to the sync adapter framework. To identify this service to the system, declare it in your app manifest by adding the following <service> element as a child element of <application>:

    <service
            android:name="com.example.android.syncadapter.AuthenticatorService">
        <intent-filter>
            <action android:name="android.accounts.AccountAuthenticator"/>
        </intent-filter>
        <meta-data
            android:name="android.accounts.AccountAuthenticator"
            android:resource="@xml/authenticator" />
    </service>

The <intent-filter> element sets up a filter that's triggered by the intent actionandroid.accounts.AccountAuthenticator, which sent by the system to run the authenticator. When the filter is triggered, the system starts AuthenticatorService, the bound Service you have provided to wrap the authenticator.

The <meta-data> element declares the metadata for the authenticator. The android:name attribute links the meta-data to the authentication framework. The android:resource element specifies the name of the authenticator metadata file you created previously.

Besides an authenticator, a sync adapter also requires a content provider. If your app doesn't use a content provider already, go to the next lesson to learn how to create a stub content provider; otherwise, go to the lessonCreating a Sync Adapter.

Creating a Stub Content Provider

THIS LESSON TEACHES YOU TO

  1. Add a Stub Content Provider
  2. Declare the Provider in the Manifest

YOU SHOULD ALSO READ

TRY IT OUT

Download the sample

BasicSyncAdapter.zip

The sync adapter framework is designed to work with device data managed by the flexible and highly secure content provider framework. For this reason, the sync adapter framework expects that an app that uses the framework has already defined a content provider for its local data. If the sync adapter framework tries to run your sync adapter, and your app doesn't have a content provider, your sync adapter crashes.

If you're developing a new app that transfers data from a server to the device, you should strongly consider storing the local data in a content provider. Besides their importance for sync adapters, content providers offer a variety of security benefits and are specifically designed to handle data storage on Android systems. To learn more about creating a content provider, seeCreating a Content Provider.

However, if you're already storing local data in another form, you can still use a sync adapter to handle data transfer. To satisfy the sync adapter framework requirement for a content provider, add a stub content provider to your app. A stub provider implements the content provider class, but all of its required methods return null or 0. If you add a stub provider, you can then use a sync adapter to transfer data from any storage mechanism you choose.

If you already have a content provider in your app, you don't need a stub content provider. In that case, you can skip this lesson and proceed to the lesson Creating a Sync Adapter. If you don't yet have a content provider, this lesson shows you how to add a stub content provider that allows you to plug your sync adapter into the framework.

Add a Stub Content Provider


To create a stub content provider for your app, extend the class ContentProvider and stub out its required methods. The following snippet shows you how to create the stub provider:

/*
 * Define an implementation of ContentProvider that stubs out
 * all methods
 */
public class StubProvider extends ContentProvider {
    /*
     * Always return true, indicating that the
     * provider loaded correctly.
     */
    @Override
    public boolean onCreate() {
        return true;
    }
    /*
     * Return an empty String for MIME type
     */
    @Override
    public String getType() {
        return new String();
    }
    /*
     * query() always returns no results
     *
     */
    @Override
    public Cursor query(
            Uri uri,
            String[] projection,
            String selection,
            String[] selectionArgs,
            String sortOrder) {
        return null;
    }
    /*
     * insert() always returns null (no URI)
     */
    @Override
    public Uri insert(Uri uri, ContentValues values) {
        return null;
    }
    /*
     * delete() always returns "no rows affected" (0)
     */
    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        return 0;
    }
    /*
     * update() always returns "no rows affected" (0)
     */
    public int update(
            Uri uri,
            ContentValues values,
            String selection,
            String[] selectionArgs) {
        return 0;
    }
}

Declare the Provider in the Manifest


The sync adapter framework verifies that your app has a content provider by checking that your app has declared a provider in its app manifest. To declare the stub provider in the manifest, add a <provider> element with the following attributes:

android:name="com.example.android.datasync.provider.StubProvider"
Specifies the fully-qualified name of the class that implements the stub content provider.
android:authorities="com.example.android.datasync.provider"
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.
android:exported="false"
Determines whether other apps can access the content provider. For your stub content provider, set the value to 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.
android:syncable="true"
Sets a flag that indicates that the provider is syncable. If you set this flag to true, you don't have to callsetIsSyncable() 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.

The following snippet shows you how to add the <provider> element to the app manifest:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.android.network.sync.BasicSyncAdapter"
    android:versionCode="1"
    android:versionName="1.0" >
    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
    ...
    <provider
        android:name="com.example.android.datasync.provider.StubProvider"
        android:authorities="com.example.android.datasync.provider"
        android:exported="false"
        android:syncable="true"/>
    ...
    </application>
</manifest>

Now that you have created the dependencies required by the sync adapter framework, you can create the component that encapsulates your data transfer code. This component is called a sync adapter. The next lesson shows you how to add this component to your app.

Creating a Sync Adapter

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:

Sync adapter class.
A class that wraps your data transfer code in an interface compatible with the sync adapter framework.
Bound Service.
A component that allows the sync adapter framework to run the code in your sync adapter class.
Sync adapter XML metadata file.
A file containing information about your sync adapter. The framework reads this file to find out how to load and schedule your data transfer.
Declarations in the app manifest.
XML that declares the bound service and points to sync adapter-specific metadata.

This lesson shows you how to define these elements.

Create a Sync Adapter Class


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.

Extend the base sync adapter class AbstractThreadedSyncAdapter

To create the sync adapter component, start by extending 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 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 ContentResolver instance. Since a second form of the constructor was added in Android platform version 3.0 to support the parallelSyncs argument, you need to create two forms of the constructor to maintain compatibility.

Note: The sync adapter framework is designed to work with sync adapter components that are singleton instances. Instantiating the sync adapter component is covered in more detail in the section Bind the Sync Adapter to the Framework.

The following example shows you how to implement AbstractThreadedSyncAdapterand its constructors:

/**
 * Handle the transfer of data between a server and an
 * app, using the Android sync adapter framework.
 */
public class SyncAdapter extends AbstractThreadedSyncAdapter {
    ...
    // Global variables
    // Define a variable to contain a content resolver instance
    ContentResolver mContentResolver;
    /**
     * Set up the sync adapter
     */
    public SyncAdapter(Context context, boolean autoInitialize) {
        super(context, autoInitialize);
        /*
         * If your app uses a content resolver, get an instance of it
         * from the incoming Context
         */
        mContentResolver = context.getContentResolver();
    }
    ...
    /**
     * Set up the sync adapter. This form of the
     * constructor maintains compatibility with Android 3.0
     * and later platform versions
     */
    public SyncAdapter(
            Context context,
            boolean autoInitialize,
            boolean allowParallelSyncs) {
        super(context, autoInitialize, allowParallelSyncs);
        /*
         * If your app uses a content resolver, get an instance of it
         * from the incoming Context
         */
        mContentResolver = context.getContentResolver();
        ...
    }

Add the data transfer code to onPerformSync()

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 onPerformSync().

To facilitate the transfer of data from your main app code to the sync adapter component, the sync adapter framework calls onPerformSync() with the following arguments:

Account
An 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.
Extras
Bundle containing flags sent by the event that triggered the sync adapter.
Authority
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.
Content provider client
ContentProviderClient for the content provider pointed to by the authority argument. AContentProviderClient is a lightweight public interface to a content provider. It has the same basic functionality as a 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.
Sync result
SyncResult object that you use to send information to the sync adapter framework.

The following snippet shows the overall structure of onPerformSync():

    /*
     * Specify the code you want to run in the sync adapter. The entire
     * sync adapter runs in a background thread, so you don't have to set
     * up your own background processing.
     */
    @Override
    public void onPerformSync(
            Account account,
            Bundle extras,
            String authority,
            ContentProviderClient provider,
            SyncResult syncResult) {
    /*
     * Put the data transfer code here.
     */
    ...
    }

While the actual implementation of onPerformSync() is specific to your app's data synchronization requirements and server connection protocols, there are a few general tasks your implementation should perform:

Connecting to a server
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.
Downloading and uploading data
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.
Handling data conflicts or determining how current the data is
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.
Clean up.
Always close connections to a server and clean up temp files and caches at the end of your data transfer.

Note: The sync adapter framework runs onPerformSync() on a background thread, so you don't have to set up your own background processing.

In addition to your sync-related tasks, you should try to combine your regular network-related tasks and add them to onPerformSync(). By concentrating all of your network tasks in this method, you conserve the battery power that's needed to start and stop the network interfaces. To learn more about making network access more efficient, see the training class Transferring Data Without Draining the Battery, which describes several network access tasks you can include in your data transfer code.

Bind the Sync Adapter to the Framework


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 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 onPerformSync() method and pass data to it.

Instantiate your sync adapter component as a singleton in the onCreate() method of the service. By instantiating the component in onCreate(), you defer creating it until the service starts, which happens when the framework first tries to run your data transfer. You need to instantiate the component in a thread-safe manner, in case the sync adapter framework queues up multiple executions of your sync adapter in response to triggers or scheduling.

For example, the following snippet shows you how to create a class that implements the bound Service, instantiates your sync adapter component, and gets the Android binder object:

package com.example.android.syncadapter;
/**
 * Define a Service that returns an IBinder for the
 * sync adapter class, allowing the sync adapter framework to call
 * onPerformSync().
 */
public class SyncService extends Service {
    // Storage for an instance of the sync adapter
    private static SyncAdapter sSyncAdapter = null;
    // Object to use as a thread-safe lock
    private static final Object sSyncAdapterLock = new Object();
    /*
     * Instantiate the sync adapter object.
     */
    @Override
    public void onCreate() {
        /*
         * Create the sync adapter as a singleton.
         * Set the sync adapter as syncable
         * Disallow parallel syncs
         */
        synchronized (sSyncAdapterLock) {
            if (sSyncAdapter == null) {
                sSyncAdapter = new SyncAdapter(getApplicationContext(), true);
            }
        }
    }
    /**
     * Return an object that allows the system to invoke
     * the sync adapter.
     *
     */
    @Override
    public IBinder onBind(Intent intent) {
        /*
         * Get the object that allows external processes
         * to call onPerformSync(). The object is created
         * in the base class code when the SyncAdapter
         * constructors call super()
         */
        return sSyncAdapter.getSyncAdapterBinder();
    }
}

Note: To see a more detailed example of a bound service for a sync adapter, see the sample app.

Add the Account Required by the Framework


The sync adapter framework requires each sync adapter to have an account type. You declared the account type value in the section Add the Authenticator Metadata File. Now you have to set up this account type in the Android system. To set up the account type, add a dummy account that uses the account type by callingaddAccountExplicitly().

The best place to call the method is in the onCreate() method of your app's opening activity. The following code snippet shows you how to do this:

public class MainActivity extends FragmentActivity {
    ...
    ...
    // Constants
    // The authority for the sync adapter's content provider
    public static final String AUTHORITY = "com.example.android.datasync.provider"
    // An account type, in the form of a domain name
    public static final String ACCOUNT_TYPE = "example.com";
    // The account name
    public static final String ACCOUNT = "dummyaccount";
    // Instance fields
    Account mAccount;
    ...
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ...
        // Create the dummy account
        mAccount = CreateSyncAccount(this);
        ...
    }
    ...
    /**
     * Create a new dummy account for the sync adapter
     *
     * @param context The application context
     */
    public static Account CreateSyncAccount(Context context) {
        // Create the account type and default account
        Account newAccount = new Account(
                ACCOUNT, ACCOUNT_TYPE);
        // Get an instance of the Android account manager
        AccountManager accountManager =
                (AccountManager) context.getSystemService(
                        ACCOUNT_SERVICE);
        /*
         * Add the account and account type, no password or user data
         * If successful, return the Account object, otherwise report an error.
         */
        if (accountManager.addAccountExplicitly(newAccount, null, null))) {
            /*
             * If you don't set android:syncable="true" in
             * in your <provider> element in the manifest,
             * then call context.setIsSyncable(account, AUTHORITY, 1)
             * here.
             */
        } else {
            /*
             * The account exists or some other error occurred. Log this, report it,
             * or handle it internally.
             */
        }
    }
    ...
}

Add the Sync Adapter Metadata File


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 /res/xml/ directory in your app project. You can give any name to the file, although it's usually called syncadapter.xml.

This XML file contains a single XML element <sync-adapter> that has the following attributes:

android:contentAuthority
The URI authority for your content provider. If you created a stub content provider for your app in the previous lesson Creating a Stub Content Provider, use the value you specified for the attributeandroid:authorities in the <provider> element you added to your app manifest. This attribute is described in more detail in the section Declare the Provider in the Manifest
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 android:authorities attribute of the <provider> element that declares your provider in your app manifest.
android:accountType
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 Add the Authenticator Metadata File. It's also the value you specified for the constant ACCOUNT_TYPE in the code snippet in the section Add the Account Required by the Framework.
Settings attributes
android:userVisible
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 Accounts 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.
android:supportsUploading
Allows you to upload data to the cloud. Set this to false if your app only downloads data.
android:allowParallelSyncs
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.
android:isAlwaysSyncable
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 false, and then call requestSync() to run the sync adapter. To learn more about running a sync adapter, see the lesson Running a Sync Adapter

The following example shows the XML for a sync adapter that uses a single dummy account and only does downloads.

<?xml version="1.0" encoding="utf-8"?>
<sync-adapter
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:contentAuthority="com.example.android.datasync.provider"
        android:accountType="com.android.example.datasync"
        android:userVisible="false"
        android:supportsUploading="false"
        android:allowParallelSyncs="false"
        android:isAlwaysSyncable="true"/>

Declare the Sync Adapter in the Manifest


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 Service you've added.

Since the sync adapter component runs code that transfers data between the network and the device, you need to request permission to access the Internet. In addition, your app needs to request permission to read and write sync adapter settings, so you can control the sync adapter programmatically from other components in your app. You also need to request a special permission that allows your app to use the authenticator component you created in the lesson Creating a Stub Authenticator.

To request these permissions, add the following to your app manifest as child elements of <manifest>:

android.permission.INTERNET
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.
android.permission.READ_SYNC_SETTINGS
Allows your app to read the current sync adapter settings. For example, you need this permission in order to call getIsSyncable().
android.permission.WRITE_SYNC_SETTINGS
Allows your app to control sync adapter settings. You need this permission in order to set periodic sync adapter runs using addPeriodicSync(). This permission is not required to call requestSync(). To learn more about running the sync adapter, see Running A Sync Adapter.
android.permission.AUTHENTICATE_ACCOUNTS
Allows you to use the authenticator component you created in the lesson Creating a Stub Authenticator.

The following snippet shows how to add the permissions:

<manifest>
...
    <uses-permission
            android:name="android.permission.INTERNET"/>
    <uses-permission
            android:name="android.permission.READ_SYNC_SETTINGS"/>
    <uses-permission
            android:name="android.permission.WRITE_SYNC_SETTINGS"/>
    <uses-permission
            android:name="android.permission.AUTHENTICATE_ACCOUNTS"/>
...
</manifest>

Finally, to declare the bound Service that the framework uses to interact with your sync adapter, add the following XML to your app manifest as a child element of <application>:

        <service
                android:name="com.example.android.datasync.SyncService"
                android:exported="true"
                android:process=":sync">
            <intent-filter>
                <action android:name="android.content.SyncAdapter"/>
            </intent-filter>
            <meta-data android:name="android.content.SyncAdapter"
                    android:resource="@xml/syncadapter" />
        </service>

The <intent-filter> element sets up a filter that's triggered by the intent action 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 SyncService. The attribute android:exported="true" allows processes other than your app (including the system) to access the Service. The attribute android:process=":sync" tells the system to run the Service in a global shared process named sync. If you have multiple sync adapters in your app they can share this process, which reduces overhead.

The <meta-data> element provides provides the name of the sync adapter metadata XML file you created previously. The android:name attribute indicates that this metadata is for the sync adapter framework. Theandroid:resource element specifies the name of the metadata file.

You now have all of the components for your sync adapter. The next lesson shows you how to tell the sync adapter framework to run your sync adapter, either in response to an event or on a regular schedule.

Running a Sync Adapter

In the previous lessons in this class, you learned how to create a sync adapter component that encapsulates data transfer code, and how to add the additional components that allow you to plug the sync adapter into the system. You now have everything you need to install an app that includes a sync adapter, but none of the code you've seen actually runs the sync adapter.

You should try to run your sync adapter based on a schedule or as the indirect result of some event. For example, you may want your sync adapter to run on a regular schedule, either after a certain period of time or at a particular time of the day. You may also want to run your sync adapter when there are changes to data stored on the device. You should avoid running your sync adapter as the direct result of a user action, because by doing this you don't get the full benefit of the sync adapter framework's scheduling ability. For example, you should avoid providing a refresh button in your user interface.

You have the following options for running your sync adapter:

When server data changes
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.
When device data changes
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.
When the system sends out a network message
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.
At regular intervals
Run a sync adapter after the expiration of an interval you choose, or run it at a certain time every day.
On demand
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.

The rest of this lesson describes each of the options in more detail.

Run the Sync Adapter When Server Data Changes


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 aBroadcastReceiver in your app. In response to this message, call ContentResolver.requestSync() to signal the sync adapter framework to run your sync adapter.

Google Cloud Messaging (GCM) provides both the server and device components you need to make this messaging system work. Using GCM to trigger transfers is more reliable and more efficient than polling servers for status. While polling requires a Service that is always active, GCM uses a BroadcastReceiver that's activated when a message arrives. While polling at regular intervals uses battery power even if no updates are available, GCM only sends messages when needed.

Note: If you use GCM to trigger your sync adapter via a broadcast to all devices where your app is installed, remember that they receive your message at roughly the same time. This situation can cause multiple instance of your sync adapter to run at the same time, causing server and network overload. To avoid this situation for a broadcast to all devices, you should consider deferring the start of the sync adapter for a period that's unique for each device.

The following code snippet shows you how to run requestSync() in response to an incoming GCM message:

public class GcmBroadcastReceiver extends BroadcastReceiver {
    ...
    // Constants
    // Content provider authority
    public static final String AUTHORITY = "com.example.android.datasync.provider"
    // Account type
    public static final String ACCOUNT_TYPE = "com.example.android.datasync";
    // Account
    public static final String ACCOUNT = "default_account";
    // Incoming Intent key for extended data
    public static final String KEY_SYNC_REQUEST =
            "com.example.android.datasync.KEY_SYNC_REQUEST";
    ...
    @Override
    public void onReceive(Context context, Intent intent) {
        // Get a GCM object instance
        GoogleCloudMessaging gcm =
                GoogleCloudMessaging.getInstance(context);
        // Get the type of GCM message
        String messageType = gcm.getMessageType(intent);
        /*
         * Test the message type and examine the message contents.
         * Since GCM is a general-purpose messaging system, you
         * may receive normal messages that don't require a sync
         * adapter run.
         * The following code tests for a a boolean flag indicating
         * that the message is requesting a transfer from the device.
         */
        if (GoogleCloudMessaging.MESSAGE_TYPE_MESSAGE.equals(messageType)
            &&
            intent.getBooleanExtra(KEY_SYNC_REQUEST)) {
            /*
             * Signal the framework to run your sync adapter. Assume that
             * app initialization has already created the account.
             */
            ContentResolver.requestSync(ACCOUNT, AUTHORITY, null);
            ...
        }
        ...
    }
    ...
}

Run the Sync Adapter When Content Provider Data Changes


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 requestSync() to tell the framework to run your sync adapter.

Note: If you're using a stub content provider, you don't have any data in the content provider and 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 requestSync() when the data changes.

To create an observer for your content provider, extend the class ContentObserver and implement both forms of its onChange() method. In onChange(), call requestSync() to start the sync adapter.

To register the observer, pass it as an argument in a call to 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 ContentResolver methods that modify your provider, such asContentResolver.insert(). If there's a match, your implementation of ContentObserver.onChange() is called.

The following code snippet shows you how to define a ContentObserver that calls requestSync() when a table changes:

public class MainActivity extends FragmentActivity {
    ...
    // Constants
    // Content provider scheme
    public static final String SCHEME = "content://";
    // Content provider authority
    public static final String AUTHORITY = "com.example.android.datasync.provider";
    // Path for the content provider table
    public static final String TABLE_PATH = "data_table";
    // Account
    public static final String ACCOUNT = "default_account";
    // Global variables
    // A content URI for the content provider's data table
    Uri mUri;
    // A content resolver for accessing the provider
    ContentResolver mResolver;
    ...
    public class TableObserver extends ContentObserver {
        /*
         * Define a method that's called when data in the
         * observed content provider changes.
         * This method signature is provided for compatibility with
         * older platforms.
         */
        @Override
        public void onChange(boolean selfChange) {
            /*
             * Invoke the method signature available as of
             * Android platform version 4.1, with a null URI.
             */
            onChange(selfChange, null);
        }
        /*
         * Define a method that's called when data in the
         * observed content provider changes.
         */
        @Override
        public void onChange(boolean selfChange, Uri changeUri) {
            /*
             * Ask the framework to run your sync adapter.
             * To maintain backward compatibility, assume that
             * changeUri is null.
            ContentResolver.requestSync(ACCOUNT, AUTHORITY, null);
        }
        ...
    }
    ...
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ...
        // Get the content resolver object for your app
        mResolver = getContentResolver();
        // Construct a URI that points to the content provider data table
        mUri = new Uri.Builder()
                  .scheme(SCHEME)
                  .authority(AUTHORITY)
                  .path(TABLE_PATH)
                  .build();
        /*
         * Create a content observer object.
         * Its code does not mutate the provider, so set
         * selfChange to "false"
         */
        TableObserver observer = new TableObserver(false);
        /*
         * Register the observer for the data table. The table's path
         * and any of its subpaths trigger the observer.
         */
        mResolver.registerContentObserver(mUri, true, observer);
        ...
    }
    ...
}

Run the Sync Adapter After a Network Message


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 ContentResolver of each app. By callingsetSyncAutomatically(), you can run the sync adapter whenever the ContentResolver receives the message.

By scheduling your sync adapter to run when the network message is sent, you ensure that your sync adapter is always scheduled to run while the network is available. Use this option if you don't have to force a data transfer in response to data changes, but you do want to ensure your data is regularly updated. Similarly, you can use this option if you don't want a fixed schedule for your sync adapter, but you do want it to run frequently.

Since the method setSyncAutomatically() doesn't disable 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 setSyncAutomatically().

The following code snippet shows you how to configure your ContentResolver to run your sync adapter in response to a network message:

public class MainActivity extends FragmentActivity {
    ...
    // Constants
    // Content provider authority
    public static final String AUTHORITY = "com.example.android.datasync.provider";
    // Account
    public static final String ACCOUNT = "default_account";
    // Global variables
    // A content resolver for accessing the provider
    ContentResolver mResolver;
    ...
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ...
        // Get the content resolver for your app
        mResolver = getContentResolver();
        // Turn on automatic syncing for the default account and authority
        mResolver.setSyncAutomatically(ACCOUNT, AUTHORITY, true);
        ...
    }
    ...
}

Run the Sync Adapter Periodically


You can run your sync adapter periodically by setting a period of time to wait between runs, or by running it at certain times of the day, or both. Running your sync adapter periodically allows you to roughly match the update interval of your server.

Similarly, you can upload data from the device when your server is relatively idle, by scheduling your sync adapter to run at night. Most users leave their powered on and plugged in at night, so this time is usually available. Moreover, the device is not running other tasks at the same time as your sync adapter. If you take this approach, however, you need to ensure that each device triggers a data transfer at a slightly different time. If all devices run your sync adapter at the same time, you are likely to overload your server and cell provider data networks.

In general, periodic runs make sense if your users don't need instant updates, but expect to have regular updates. Periodic runs also make sense if you want to balance the availability of up-to-date data with the efficiency of smaller sync adapter runs that don't over-use device resources.

To run your sync adapter at regular intervals, call addPeriodicSync(). This schedules your sync adapter to run after a certain amount of time has elapsed. Since the sync adapter framework has to account for other sync adapter executions and tries to maximize battery efficiency, the elapsed time may vary by a few seconds. Also, the framework won't run your sync adapter if the network is not available.

Notice that 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 AlarmManager. If you use the method setInexactRepeating() to set time-of-day triggers that have some variation, you should still randomize the start time to ensure that sync adapter runs from different devices are staggered.

The method addPeriodicSync() doesn't disable 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 toaddPeriodicSync(); the flags that are not allowed are described in the referenced documentation foraddPeriodicSync().

The following code snippet shows you how to schedule periodic sync adapter runs:

public class MainActivity extends FragmentActivity {
    ...
    // Constants
    // Content provider authority
    public static final String AUTHORITY = "com.example.android.datasync.provider";
    // Account
    public static final String ACCOUNT = "default_account";
    // Sync interval constants
    public static final long 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;
    // 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,
                Bundle.EMPTY,
                SYNC_INTERVAL);
        ...
    }
    ...
}

Run the Sync Adapter On Demand


Running your sync adapter in response to a user request is the least preferable strategy for running a sync adapter. The framework is specifically designed to conserve battery power when it runs sync adapters according to a schedule. Options that run a sync in response to data changes use battery power effectively, since the power is used to provide new data.

In comparison, allowing users to run a sync on demand means that the sync runs by itself, which is inefficient use of network and power resources. Also, providing sync on demand leads users to request a sync even if there's no evidence that the data has changed, and running a sync that doesn't refresh data is an ineffective use of battery power. In general, your app should either use other signals to trigger a sync or schedule them at regular intervals, without user input.

However, if you still want to run the sync adapter on demand, set the sync adapter flags for a manual sync adapter run, then call ContentResolver.requestSync().

Run on demand transfers with the following flags:

SYNC_EXTRAS_MANUAL
Forces a manual sync. The sync adapter framework ignores the existing settings, such as the flag set bysetSyncAutomatically().
SYNC_EXTRAS_EXPEDITED
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.

The following code snippet shows you how to call requestSync() in response to a button click:

public class MainActivity extends FragmentActivity {
    ...
    // Constants
    // Content provider authority
    public static final String AUTHORITY =
            "com.example.android.datasync.provider"
    // Account type
    public static final String ACCOUNT_TYPE = "com.example.android.datasync";
    // Account
    public static final String ACCOUNT = "default_account";
    // Instance fields
    Account mAccount;
    ...
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ...
        /*
         * Create the dummy account. The code for CreateSyncAccount
         * is listed in the lesson Creating a Sync Adapter
         */         mAccount = CreateSyncAccount(this);
        ...
    }
    /**
     * Respond to a button click by calling requestSync(). This is an
     * asynchronous operation.
     *
     * This method is attached to the refresh button in the layout
     * XML file
     *
     * @param v The View associated with the method call,
     * in this case a Button
     */
    public void onRefreshButtonClick(View v) {
        ...
        // Pass the settings flags by inserting them in a bundle
        Bundle settingsBundle = new Bundle();
        settingsBundle.putBoolean(
                ContentResolver.SYNC_EXTRAS_MANUAL, true);
        settingsBundle.putBoolean(
                ContentResolver.SYNC_EXTRAS_EXPEDITED, true);
        /*
         * Request the sync for the default account, authority, and
         * manual sync settings
         */
        ContentResolver.requestSync(mAccount, AUTHORITY, settingsBundle);
    }

Android开发训练之第五章第六节——Transferring Data Using Sync Adapters的更多相关文章

  1. Android开发训练之第五章第四节——Syncing to the Cloud

    Syncing to the Cloud GET STARTED DEPENDENCIES AND PREREQUISITES Android 2.2 (API level 8) and higher ...

  2. Android开发训练之第五章第七节——Transmitting Network Data Using Volley

    Transmitting Network Data Using Volley GET STARTED DEPENDENCIES AND PREREQUISITES Android 1.6 (API L ...

  3. Android开发训练之第五章——Building Apps with Connectivity &amp&semi; the Cloud

    Building Apps with Connectivity & the Cloud These classes teach you how to connect your app to t ...

  4. Android开发训练之第五章第三节——Transferring Data Without Draining the Battery

    Transferring Data Without Draining the Battery GET STARTED DEPENDENCIES AND PREREQUISITES Android 2. ...

  5. Android开发训练之第五章第五节——Resolving Cloud Save Conflicts

    Resolving Cloud Save Conflicts IN THIS DOCUMENT Get Notified of Conflicts Handle the Simple Cases De ...

  6. Android开发艺术探索第五章——理解RemoteViews

    Android开发艺术探索第五章--理解RemoteViews 这门课的重心在于RemoteViews,RemoteViews可以理解为一种远程的View,其实他和远程的Service是一样的,Rem ...

  7. Android开发艺术探索笔记——第一章:Activity的生命周期和启动模式

    Android开发艺术探索笔记--第一章:Activity的生命周期和启动模式 怀着无比崇敬的心情翻开了这本书,路漫漫其修远兮,程序人生,为自己加油! 一.序 作为这本书的第一章,主席还是把Activ ...

  8. Android群英传笔记——第五章:Android Scroll分析

    Android群英传笔记--第五章:Android Scroll分析 滑动事件算是Android比较常用的效果了,而且滑动事件他本身也是有许多的知识点,今天,我们就一起来耍耍Scroll吧 一.滑动效 ...

  9. 《NodeJs开发指南》第五章微博开发实例的中文乱码问题

    在<NodeJs开发指南>第五章,按照书中的要求写好微博实例后,运行代码,发现中文显示出现乱码,原因是:views文件夹下的ejs文件的编码格式不是utf-8. 解决方法:以记事本方式打开 ...

随机推荐

  1. C&num;中自己动手创建一个Web Server(非Socket实现)

    目录 介绍 Web Server在Web架构系统中的作用 Web Server与Web网站程序的交互 HTTPListener与Socket两种方式的差异 附带Demo源码概述 Demo效果截图 总结 ...

  2. 取模&lpar;mod&rpar;

    取模(mod) [题目描述] 有一个整数a和n个整数b_1, …, b_n.在这些数中选出若干个数并重新排列,得到c_1,…, c_r.我们想保证a mod c_1 mod c_2 mod … mod ...

  3. javascript零散要点收集

    1.this永远指向函数对象的所有者 2.ECMA-262 把对象(object)定义为“属性的无序集合,每个属性存放一个原始值.对象或函数”.严格来说,这意味着对象是无特定顺序的值的数组. 3.pr ...

  4. lightoj 1013

    思路:动态规划.设dp[i][j][k]表示用第一个串的前i隔字符和第二个串的前k隔字符组成长度为i的串的个数,那么:若s1[j+1] == s2[k+1] dp[i+1][j+1][k+1] += ...

  5. Error&colon;Could not determine Java version-- 关于Android Studio JDK设置和JVM version设置

    最近在装AS的时候遇到一个问题,新建工程后,编译报错,Error:Could not determine Java version 不言而喻:可定是JDK的问题,网上查到2中可能性 第一:就是JDK路 ...

  6. java中利用dom4j解析XML文件

    官网下载Dom4j地址:https://dom4j.github.io/ 注意:使用Dom4j开发,需下载dom4j相应的jar文件 题目:后台利用dom4j解析student.xml文件,并返回Li ...

  7. C&plus;&plus; 解析Json——jsoncpp

    JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式,和xml类似,本文主要对VS2008中使用Jsoncpp解析json的方法做一下记录.Jsoncpp是个跨 ...

  8. Java-IO:复制文件

    import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStrea ...

  9. ubuntu中更新&period;netcore到2&period;1版本

    如果需要安装新版本到dotnetcore,需要先卸载旧版本(https://github.com/dotnet/core/blob/master/release-notes/download-arch ...

  10. nginx——防盗链功能

    我们经常会看到在浏览某一图片时会弹出一“403权限禁止”错误,这说明有可能正在浏览的这个网站用到的图片在盗用别的网站图片,而被盗用的网站采用了防盗链技术.那么怎样才能不让自己的网站受害呢? 下面我来介 ...