Bhagwad Jal Park
Bhagwad Jal Park

Reputation: 1123

Syncadapter onPerformSync being called twice the first time

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.

Upvotes: 9

Views: 8061

Answers (3)

jetzter
jetzter

Reputation: 106

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

Upvotes: 4

Zapek
Zapek

Reputation: 486

addAccountExplicitely() will cause a sync for all accounts that have an unknown syncable state (which includes your newly added SyncAdapter).

The problem is that it can take from a few seconds to a few minutes to perform, depending on how many other apps with a SyncAdapter and configured accounts are installed.

addPeriodicSync() (or requestSync()) will perform a sync immediately, which is desirable in the case that the user needs to see data as soon as possible when launching the app.

There's not much you can do, other than making sure your syncs are optimized to be quick in the case that no data between the client and server changed.

Upvotes: 1

Joachim
Joachim

Reputation: 901

Are you requesting a sync apart from the addPeriodicSync()?

It should sync by itself when you first add the account. So any extra sync requests would account for the double sync.

If that doesn't help, you can always save in preferences the time of the last sync and check against that every sync, so that you can limit the sync frequency to whatever you want.

Hope that helps!

Upvotes: 2

Related Questions