在Android中使用SyncAdapter同步数据全攻略

参考资料:

  1. 官方文档
  2. csdn博客

SyncAdapter是什么?

SyncManager是Android提供的一个同步框架,该框架实施了许多最佳做法,它允许Android应用使用Google应用中实现高效同步的一个基本框架。
它实际上是一个数据集中点,将所有的数据传输都放到同一个地方,以便操作系统智能地安排数据传输,优化电池性能。
你可以通过SyncAdapter来使用该框架处理同步请求。

为什么使用SyncAdapter?

SyncAdapter可以智能安排数据传输,如检查网络连接、下载失败后重试等。可以根据不同条件自动发起数据传输,如服务器数据变更、定时同步等。
使用SyncAdapter可以加快应用的加载时间、实现离线功能,可以在数据及时同步和减少网络调用以节约电池电量之间达到一种平衡局面。

何时使用SyncAdapter?

SyncAdapter适用于需要同步本地数据和在线账户信息的应用,如电子邮件的定时收取、笔记应用的云备份、天气应用的及时同步等。
SyncAdapter设计为必须与用户账户绑定,即使你的应用不需要账户认证,也需要实现相关的类来处理账户,并可以将其隐藏。

如何创建SyncAdapter?

1. 创建Authenticator类

该类继承了AbstractAccountAuthenticator类,用于管理账户认证。如果你的应用不需要账户认证,可以提供一个仅包含方法实现的类,Authenticator的信息将被忽略。
以下是Android提供的无需账户认证的样例代码,如果需要创建真实管理用户账户的Authenticator,请参阅AbstractAccountAuthenticator文档

/*
 * 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();
    }
}

2. 创建AuthenticatorService服务

此服务提供给SyncAdapter framework,用于调用Authenticator的方法。
onCreat()中创建Authenticator对象,在onBind()中返回一个binder对象用于在Authenticator和framework间传输数据。
下面是Android提供的一个样例代码:

/**
 * 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();
    }
}

3. 添加Authenticator的元数据文件

元数据写在一个xml文件中,用于声明账户类型和一些显示给用户的信息,保存在/res/xml/目录。
文件名自定义,一般定义为authenticator.xml,根标签为<account-authenticator>,一般有以下属性:

  1. android:accountType
    framework把账户类型作为识别SyncAdapter的内部标识,对于需要验证账户的应用,账户类型会和账户名一起发送给服务器进行验证;对于不需要验证的应用,也要提供账户类型,用于标明一个控制域,framework用这个账户类型类管理你的SyncAdapter,但是不会发送给服务器。
  2. android:icon
    指向用做图标的Drawable资源。如果在res/xml/syncadapter.xml 设置了android:userVisible="true” 属性将Sync adapter对用户可见,则必须要提供一个图标资源。它将显示在“设置”应用的“账号”一项中。
  3. android:smallIcon
    小图标,根据屏幕尺寸可能在设置中代替icon属性。
  4. android:label
    标识账户类型,一般为应用名。

以下为样例代码:

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

4. 在清单文件中声明AuthenticatorService

样例代码:

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

<intent-filter>设置了通过actionandroid.accounts.AccountAuthenticator启动的filter。这个action是由系统发送的。当被触发时,系统会启动封装了你的Authtenticator的AuthenticatorService。
<meta-data>声明了authenticator的元数据。通过android:name 属性将meta-data与认证框架关联。android:resource指定元数据文件。

5. 创建ContentProvider

SyncManager同步框架被设计用来与ContentProvider框架协作,需要一个ContentProvider来存储本地数据。使用ContentProvider的好处与其创建方法此处不再细述,之后会在另一篇笔记中记录。
如果你已经将本地数据存储为别的格式,无法实现ContentProvider,那么可以创建一个虚拟的ContentProvider,实现必要的方法返回null或0。之后可以通过SyncAdapter按照自己的方式传输数据。
虚拟ContentProvider的样例代码:

/*
 * 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 no type for MIME type
     */
    @Override
    public String getType(Uri uri) {
        return null;
    }
    /*
     * 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;
    }
}

6. 在清单文件中声明ContentProvider

provider的声明方法不再细述,主要是添加android:syncable属性为true,使其支持同步。

<provider
    android:name="com.example.android.datasync.provider.StubProvider"
    android:authorities="com.example.android.datasync.provider"
    android:exported="false"
    android:syncable="true"/>

至此完成了同步框架所需要的全部依赖项。

7. 创建SyncAdapter类

  1. 继承AbstractThreadedSyncAdapter基类,在构造方法中做一些初始化设置,如获取ContentResolver实例等。
  2. onPerformSync()方法中添加数据传输的代码,框架将自动将其放在后台线程中运行。除同步相关任务外,也应将网络相关的任务放在此处,将网络操作集中处理可以降低频繁发起网络的功耗。
  3. 可添加辅助方法syncImmediately(),调用此方法来立即执行同步,可用于“刷新”操作:
    public static void syncImmediately(Context context) {
        Bundle bundle = new Bundle();
        
        //将此同步放在同步请求队列前面,立即进行同步而不延迟
        bundle.putBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, true);
        //忽略当前设置强制发起同步
        bundle.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true);
        ContentResolver.requestSync(getSyncAccount(context),
                context.getString(R.string.content_authority), bundle);
    }
    
    
  4. 由于请求同步时需要一个同步账户,可添加辅助方法getSyncAccount()来获取账户。
    此方法与第九步添加Account的功能相同,只是将代码放在了同步框架代码中。
    提供一个虚拟账户的示例:
    public static Account getSyncAccount(Context context) {
        // Get an instance of the Android account manager
        AccountManager accountManager =
                (AccountManager) context.getSystemService(Context.ACCOUNT_SERVICE);
    
        // Create the account type and default account
        Account newAccount = new Account(
                context.getString(R.string.app_name), context.getString(R.string.sync_account_type));
    
        // If the password doesn't exist, the account doesn't exist
        if ( null == accountManager.getPassword(newAccount) ) {
    
        /*
         * 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)) {
                return null;
            }
            /*
             * If you don't set android:syncable="true" in
             * in your <provider> element in the manifest,
             * then call ContentResolver.setIsSyncable(account, AUTHORITY, 1)
             * here.
             */
    
        }
        return newAccount;
    }
    
    

8. 创建SyncService服务

该服务用于将SyncAdapter开放给framework调用,即将SyncAdapter的binder对象传给framework。通过这个binder,framework即可调用onPerformSync()方法。
onCreate()中以单实例形式实例化SyncAdapter,这样会将SyncAdapter的实例化延迟到framework首次传输数据创建Service的时候执行。实例化过程须保证线程安全,避免将多次同步响应添加到队列。
示例代码:

/**
 * 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();
    }
}

9. 添加Account

framework要求每个SyncAdapter必须有一个账户类型,对应第三步中Authenticator的元数据文件,需要在Android系统中设置账户类型:调用addAccountExplicitly()方法,天价一个具有账户类型的虚拟账户。最好是在打开应用时的onCreate()中调用此方法。以下是Android提供的示例代码:

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.
             */
        }
    }
    ...
}

10. 添加SyncAdapter的元数据文件

元数据制订了SyncAdapter的账户类型、对应ContentProvider的Authority、系统和SyncAdapter相关的部分UI以及其他一些同步相关的标识。文件名一般取syncadapter.xml,保存在/res/xml/目录下,根标签为<sync-adapter>

<?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"
    
    //是否允许SyncAdapter多实例同时运行,如果应用需要支持多账号并发传输时才使用此标识,如果没有并发的数据传输则此标识无效
    android:allowParallelSyncs="false"
    
    //framework是否可以在任意时刻运行SyncAdapter,如果仅希望通过程序控制同步发起,则设为false,然后通过调用``requestSync()``发起。
    android:isAlwaysSyncable="true"/>

11. 在清单中声明SyncAdapter

需要添加四个权限,并声明SyncService:

<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"/>
...
    <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>
</manifest>

<intent-filter>设置了一个由Action为android.content.SyncAdapter的Intent触发的过滤器,系统要运行SyncAdapter时会发送这个Intent。当过滤器被触发时,系统会创建绑定用的Service,本例中即SyncService。
android:exported="true"允许出此应用之外的进程来访问这个Service。
android:process=":sync"告诉系统在名为sync 的全局共享的进程中运行这个Service。如果你的应用中有多个SyncAdapter,他们可以共享这个进程,可以降低一些消耗。
<meta-data>元素规定了之前创建SyncAdapter元数据XML文件。android:name属性说明这个元数据是给同步框架的。android:resource元素指定了元数据文件的名字。

如何运行SyncAdapter?

  • 可以用以下几种方式运行SyncAdapter:
    • 服务端数据变化时
    • 本地数据变化时
    • 系统发送网络消息时
    • 固定时间间隔或时间点
    • 手动发起

服务端数据变更时同步

当服务端数据发生变更,服务端发送一条特殊的消息到应用的BroadcastReceiver,然后调用ContentResolver.requestSync()发起同步。Google Cloud Messaging提供了发送此消息的服务端和客户端组件,使用GCM比轮询服务器更可靠更有效率。以下是示例代码:

public class GcmBroadcastReceiver extends BroadcastReceiver {
    ...
    // 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);
            ...
        }
        ...
    }
    ...
}

本地数据变化时同步

如果本地数据使用ContentProvider管理,则可以方便地实现当数据更新时同步到服务端。为ContentProvider注册一个Observer,当数据变化后,framework会调用这个Observer,在Observer中调用requestSync()开启同步。
要给ContentProvider创建Observer,只需继承ContentObserver并实现其中的onChange()方法,在onChange()方法中调用requestSync()运行SyncAdapter。
注册Observer,将其作为参数传入registerContentObserver(),还需传入需要监听的URI。ContentProvider框架会将此URI与传入的URI进行比较,匹配成功则调用ContentProvider.onChange()方法。
示例代码:

public class MainActivity extends FragmentActivity {
    ...
    // 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);
        ...
    }
    ...
}

网络消息触发后同步

当网络连接可用时,Android系统会每隔几秒钟发送一条消息来保持手机的TCP/IP连接打开。这个消息也会到达每个应用的ContentResolver。
通过调用setSyncAutomatically(),可以设置ContentResolve在收到消息时自动发起同步。
通过设置在收到网络消息时发起同步,能确保在网络可用时发起同步。 如果你不需要在数据变化时立即强制发起同步,但是又想要保证数据有规律的更新,则可以使用这个选项。类似的,如果你不希望按照固定的时间间隔来同步但又希望能够频繁点的同步的话,也可以使用这个选项。
由于setSyncAutomatically()不会禁用addPeriodicSync(),因此SyncAdapter可能会被频繁的重复触发。因此如果要定期的触发同步,就应该禁用掉setSyncAutomatically()

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);
        ...
    }
    ...
}


定时同步

设置固定的时间点或时间间隔进行同步。
调用addPeriodicSync()设置固定的时间间隔。
如果调用setInexactRepeating()设置触发时间,那么时间应设为随机,保证不同设备的同步时间错开。

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);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            SyncRequest request = new SyncRequest.Builder()
                    .syncPeriodic(SYNC_INTERVAL, FLEX_TIME)                                
                    .setSyncAdapter(ACCOUNT, AUTHORITY)
                    .setExtras(Bundle.EMPTY).build();    
            ContentResolver.requestSync(request);
        } else {
            ContentResolver.addPeriodicSync(
                ACCOUNT,
                AUTHORITY,
                Bundle.EMPTY,
                SYNC_INTERVAL);
        }
        ContentResolver.setSyncAutomatically(ACCOUNT, AUTHORITY, true);
        ...
    }
    ...
}

手动请求同步

这是最不建议的同步策略,浪费了SyncAdapter同步框架的优点。如果还是需要手动发起同步,那么设置手动同步的flag,然后调用ContentResolver.requestSync()。需要使用以下flag:
SYNC_EXTRAS_MANUAL
强制发起手动同步,同步框架会忽略当前的一些设置,比如自动同步开关状态。
SYNC_EXTRAS_EXPEDITED
强制立即发起同步。如果不设置这个选项,系统为了优化功耗可能会等待几秒钟,将一段时间内的几次同步合并发起。
此步骤与SyncAdapter中的syncImmediately()方法功能相同,以下是Android提供的示例代码:

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同步框架发起同步判断条件
所有同步发起时会判断以下属性:
Provider的isSyncable
SyncAdapter的isAlwaysSyncable
自动同步(SYNC_EXTRAS_MANUAL 为false)发起时除以上之外,还要判断以下:
系统总同步开关( getMasterSyncAutomatically)
SyncAdapter同步开关( getSyncAutomatically)
若是通过调用ContentResolver 的notifyChange发起自动同步时会带SYNC_EXTRAS_UPLOAD标志,Android设计原意是仅将本地数据更新至服务端。此时SyncAdapter中的supportsUploading若是false,则不能发起自动同步。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,185评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,445评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,684评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,564评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,681评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,874评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,025评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,761评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,217评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,545评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,694评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,351评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,988评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,778评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,007评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,427评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,580评论 2 349

推荐阅读更多精彩内容