I just created an Account for my app.
The account is visible in settings
I set syncable="true" in my XML
I can perform a manual sync by pressing the settings -> onPerformSync
is called
I can perform a "code" sync by calling ContentResolver.requestSync -> onPerformSync is called
And of course, yes, the sync is enabled in settings. I don't use any power saver.
I also followed all the steps from here: https://stackoverflow.com/a/5255360/327402
This is my code to get the sync by code
AccountManager am = AccountManager.get(this);
Account[] accounts = am.getAccountsByType(ACCOUNT);
//Log.e("DEBUG", "Accounts: " + accounts.length);
if (accounts.length == 0) {
Account account = new Account(getString(R.string.app_name), ACCOUNT);
ContentResolver.setIsSyncable(account, AUTHORITY, 1);
ContentResolver.addPeriodicSync(account, AUTHORITY, new Bundle(), 7200);
ContentResolver.setSyncAutomatically(account, AUTHORITY, true);
if (am.addAccountExplicitly(account, "pass1", null))
Log.i("DEBUG", "account Created: " + account.name + ", " + account.type);
else
Log.i("DEBUG", "addAccountExplicitly returned false");
}
else{
ContentResolver.requestSync(accounts[0], AUTHORITY, new Bundle());// THIS IS WORKING!!!
}
}
So, everything looks correct and fine.
But unfortunately, I cannot get a periodic sync! When I open the settings, accounts, I see the account and the date and time is the time when I performed the sync by code, or manually.
Any idea on what I did wrong, or what I forgot?
Rewrite
I have put together a sample project on GitHub that demonstrates a working SyncAdapter. The project is here.
I have only tried this on an emulator with API 17 since I didn't want to wait around an hour or so (maybe longer now) for a sync to happen. I would suggest that you take this route as well.
On API 17, this demo will do a sync every 30 seconds or so. Everything runs out of the main activity with stub support classes: SyncAdapter, StubProvider, etc. The only thing the sync adapter does is to log a message to logcat that it has run.
I don't really see anything wrong with your code other than, perhaps, the order of the calls to set up the sync is incorrect. Take a look at the call order in the demo for an example of what works.
I hope you find this useful.
(I did this on Android Studio 3.0 Canary 5. I hope this is not an issue.)
One important thing to remember is that the periodic sync time is not guaranteed and may not be accurate, this is mentioned in the documentation:
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.
I would also try to create the account using AccountManager and not by creating new Account()
manager.addAccountExplicitly(account, null, null)
And last thing, if this account was just created than you may want to force only the first sync right after you set the automatic sync settings:
if (accountJustCreated) {
SyncAdapter.performSync();
}
Related
I'm using a Sync Adapter to perform synchronization for my app. It has set automatic sync:
// Inform the system that this account supports sync
ContentResolver.setIsSyncable(account, AuthenticatorService.AUTHORITY, 1);
// Inform the system that this account is eligible for auto sync when the network is up
ContentResolver.setSyncAutomatically(account, AuthenticatorService.AUTHORITY, true);
If I request the sync:
Bundle b = new Bundle();
// Disable sync backoff and ignore sync preferences. In other words...perform sync NOW!
b.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true);
b.putBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, true);
ContentResolver.requestSync(AuthenticatorService.GetAccount(), AuthenticatorService.AUTHORITY, b);
it works well -- as soon as the network is up, SyncAdapter.onPerformSync() is called. However, there's one problem: if the network is up but not connected to Internet (hotspot without tethering for instance), onPerformSync() is still called.
Of course, since it can't reach the server, sync code fails. What is worse, the system thinks everything went OK and wont do any more sync calls unless my data change.
My question is: can I either mark the synchronization as not successful so the system will try it later (for instance on another CONNECTED event or in a few seconds) or better to tell the system to perform the sync only when real Internet connectivity is present?
I could monitor the network state and do a connectivity check by myself but that would defy the whole purpose of sync adapters, wouldn't it. Only workaround I can think of is to schedule periodic synchronization (using ContentResolver.addPeriodicSync()) and when my synchronization code succeeds, turn it off. But I'm not very happy about this either (draining battery).
EDIT: As for the first part of my question, this answer is what I was looking for. It partially solves my problem but if there's consecutive number of "non-working" connections, when the device is associated with the real connection, it would take tens of minutes to sync (because of the exponential backoff algorithm).
Thanks to csenga's suggestion I found a solution: use GcmNetworkManager to schedule the synchronization. It looks a bit stupid as I have to declare yet another service just to call ContentResolver.requestSync() and if something goes wrong I have to reschedule my task instead of setting the SyncResult.stat flags (I'm virtually duplicating Sync Adapter's job here) but still better than to handle my own NETWORK_STATE_CHANGED_ACTION receiver.
If for a particular calendar sync account in android I call following two in given order,
ContentResolver.addPeriodicSync(account,CalendarContract.AUTHORITY, result, 120);
ContentResolver.setSyncAutomatically(account,CalendarContract.AUTHORITY, true);
What would be given priority ? As per documentation
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.
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.
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);
}
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!
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.
I use AccountManager addAccountExplicitly() to add an account to AccountManager.
I then call
ContentResolver.setSyncAutomatically(account, ContactsContract.AUTHORITY, true);
After doing that, when looking in the device's "Accounts & sync" screen, i see my account BUT when I click on in, I see that the "Sync contacts" check box does not appear.
It may take a few minutes for it to appear...
I found out that if I listen to the account being added, and after a delay (2 secs) call:
ContentResolver.requestSync(account, ContactsContract.AUTHORITY, new Bundle());
It is being synced.
1.why does it take time for the account to sync?
2.is this the correct way to make the account sync immediately? (will this always work?)
I had a similar issue and the edited part in this answer helped me. First time sync loops indefinitely
On the first sync, call cancelSync and try sync again.
ContentResolver.cancelSync(account, authority);
Hi
I am implementing an account sync adapter and i am trying to make a preference screen in settings under Account and Sync for the user to be able to change the sync interval. what i want to know is how can i make the adapter sync at the specified time on the preference screen? does android provide a method for that? what i was thinking of doing was saving the time of the last successful sync and comparing the passed time since then with the period stored in preferences. is this the right way to do it or is there a cleaner solution to my problem?
What you can do is call the method addPeriodicSync(Account account, String authority, Bundle extras, long pollFrequency)
Specifies that a sync should be requested with the specified the account, authority, and extras at the given frequency.
I think it will solve your problem.
For an example, see this commit where I added an option to change the sync interval in one of my apps. This commit then expanded on that by dynamically choosing how this is done, based on the api level of the os its currently running on, so it uses the built in method for 8+, and the alarm for <8.
The gist of it is that setting the interval on api <8 creates an alarm using the AlarmManager api with the specified interval that triggers a BroadcastReceiver to call requestSync.