Syncadapter onPerformSync being called twice the first time

Bhagwad Jal Park picture Bhagwad Jal Park · Aug 20, 2012 · Viewed 7.9k times · Source

My syncadapter works well, except for one thing. After the user installs the application, my app syncs twice. Later, if I manually sync it in "settings" it syncs just once as expected. It's just the very first run of the app that this happens.

Here's the code in my "onCreate" that creates account if not already created and sets up the syncadapter. Any ideas on what I'm doing wrong?

    if (accountManager.addAccountExplicitly(appAccount, null, null)) {
       ContentResolver.setIsSyncable(appAccount, PROVIDER, 1);
       ContentResolver.setSyncAutomatically(appAccount, PROVIDER, true);

       Bundle extras = new Bundle();
       extras.putBoolean("dummy stuff", true);
       ContentResolver.addPeriodicSync(appAccount, PROVIDER, extras, 43200);
    }

My desired behavior is for the app to sync once immediately after installation and then periodically as per the "addPeriodicSync" statement.

Answer

jetzter picture jetzter · Jan 14, 2014

I observed this behavior as well.

It is correct, that addAccountExplicit() will trigger a system-wide account resync of stale accounts.

Clarificiation

However, Zapek's observation about addPeriodic sync or request sync being "immediate" syncs, is not quite correct. Both are just queued. Additionally the following holds for addPeriodicSync():

These periodic syncs honor the "syncAutomatically" and "masterSyncAutomatically" settings. Although these sync are scheduled at the specified frequency, it may take longer for it to actually be started if other syncs are ahead of it in the sync operation queue. This means that the actual start time may drift. (Documentation)

Pertaining to your problem

What you experience is described in the training on running sync adapters:

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 to addPeriodicSync(); the flags that are not allowed are described in the referenced documentation for addPeriodicSync(). Android Training Sync Adapter

Google's own solution looks like yours, with a lower frequency even (60*60=3600):

    if (accountManager.addAccountExplicitly(account, null, null)) {
        // Inform the system that this account supports sync
        ContentResolver.setIsSyncable(account, CONTENT_AUTHORITY, 1);
        // Inform the system that this account is eligible for auto sync when the network is up
        ContentResolver.setSyncAutomatically(account, CONTENT_AUTHORITY, true);
        // Recommend a schedule for automatic synchronization. The system may modify this based
        // on other scheduled syncs and network utilization.
        ContentResolver.addPeriodicSync(
                account, CONTENT_AUTHORITY, new Bundle(),SYNC_FREQUENCY);
        newAccount = true;
    }

Proposition

I propose using the SyncStats in onPerformSync() to actually return some information about your initial sync to the system, so it can schedule more efficiently.

syncResult.stats.numEntries++; // For every dataset

this may not help if the other task is already scheduled - investigating

Additionally one may set up a flag 'isInitialOnPerformSync' (w. sharedPreferences), to cause other tasks to back up.

syncResult.delayUntil = <time>;

I personally am not really fan of creating a fixed no sync timeframe after the initial sync.

Further Considerations - Initial Sync Immediately

As stated in the clarification, the sync will not run immediately with your settings. There is a solution, that will let you sync immediately. This will not influence the sync settings, and will not cause them to backoff, which is why this does not solve your problem, but it has the effect that your user will not have to wait for sync to kick in. Important if you use this to display the main content in your app this way.

Code: Set up a flag for isInitialSync inside your normal app process (which you save e.g. in defaultSharedPreferences). You can even use the Upon the initial completion of the installation or login (if authentication is required) you can invoke an immediate sync as follow.

/**
 * Start an asynchronous sync operation immediately. </br>
 *
 * @param account
 */
public static void requestSyncImmediately(Account account) {
     // Disable sync backoff and ignore sync preferences. In other words...perform sync NOW!
    Bundle settingsBundle = new Bundle();
    settingsBundle.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true);
    settingsBundle.putBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, true);
    // Request sync with settings
    ContentResolver.requestSync(account, SyncConstants.CONTENT_AUTHORITY, settingsBundle);
}