How to schedule synchronization for Internet connectivity only - android

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.

Related

How much data and power does firebase child listener consume?

I have taken a look at earlier posts regarding firebase child listeners (How firebase listener actually work?). But I still could not find what I was looking for.
I have an android app with a foreground service that runs full time and that has a child listener in it. Based on any childAdded/updated/deleted operations, some operations are performed by the service.
The problem I face is that there is lot of power usage and it has to do with the internet data sent/received (based on battery reading). I am pretty sure that the child listener has been optimized to reduce power and network consumption.
I would like to know how the child listener works (i.e. exponential backoff, etc.) in the following scenarios:
1) There is NO child added on that child-listener reference node for over 20 minutes
How often does the android device connect to the child listener in this case? And will there be any power consumption due to maintaining the open socket connection with the server?
2) After 60 minutes of no changes on the firebase node, suddenly there is a child added/updated/deleted operation and the listener fires.
What is the power consumption in this case? And does the device again fire a 3.5kB overhead to reconnect with the server?
3) If I turn off database (for firestore or realtimeDB) using the goOffline method (or the equivalent method for firestore), how much time does it take before the connection is disconnected? Based on some android profiling that was done by me, I found a nearly five minutes gap before the connection was terminated!
As long as there is an active listener, there is an open socket between your Android device and the Firebase Database server that it is connected to. There is no "exponential backoff" in that case, the listener just stays connected.
If you don't want to keep such an open connection, you can periodically calls addListenerForSingleValueEvent or explicitly call goOffline()/goOnline() from your code. A third option is to access Firebase through its REST API.
Each time the client reconnects to the server, it'll have to go through an SSL handshake. There is no way to prevent this on Android.
The 5 minute delay is likely just the socket time-out on your device. There is nothing the Firebase client can do about that, as it's a normal part of the operation of sockets.

GCM Network Manager losing jobs

I am trying to use GCM Network Manager to send logs to out backend service. We have an alarm running about every hour that creates a OneoffTask that, when executed, will call the backend service with the log message.
This works, BUT a very large amount of the Tasks are lost (way more than half). At first, I thought it has something to do with our backend, or the network, but after adding a ton of file logging, it turns out that onRunTask in the service is never triggered for these tasks (but they are definitely getting scheduled. Why are these lost? Am I misunderstanding the API, or is OneoffTasks simply not reliable?
This is how the OneoffTask is scheduled:
GcmNetworkManager.getInstance(context).schedule(new OneoffTask.Builder()
.setService(AvroLogService.class)
.setExtras(bundle)
// A mandatory tag which identifies the task
// We add a unique hash to the tag to make sure that
// tasks are logged and not thrown away as dupes.
// See: http://stackoverflow.com/q/34528960/304262
.setTag(java.util.UUID.randomUUID().toString())
// Persist to disk, even across boots:
.setPersisted(true)
// Sets a time frame for the execution of this task in seconds.
// This specifically means that the task can either be
// executed right now, or at latest at a certain point:
.setExecutionWindow(0, TWO_WEEKS_IN_SECONDS)
.build());
Again, not this works, but only part of the messages. For the messages that are subsequently lost, the above code is
definitely executed (I've added file logging to verify this), but there is never a corresponding onRunTask triggered for the lost ones.
I have verified that:
Manifest is updated according to the Network Manager Implementation Guide (https://developers.google.com/cloud-messaging/network-manager)
AvroLogService (my service) extends GcmTaskService
It overrides onRunTask
The app has RECEIVE_BOOT_COMPLETED permission.
AvroLogService does NOT override onStartCommand.
I'm lost. Can someone share insights on this?
As answer above execution time range may be to big. Also I think that You want execute event periodically try use PeriodicTask.Builder instead ofOneoffTask.Builder
As I guess your constant TWO_WEEKS_IN_SECONDS really means 2 WEEKS. In this case you task is eligible for execution in any point of time from now to 2 WEEKS. So, this task doesn't have to be executed every hour.
Try to set execution window in range of one hour or even less (.setExecutionWindow(0, HALF_AN_HOUR_IN_SECONDS))
See google api docs

Posting data to server every 15 minutes using Sync Adapter and Alarms

Requirement - I need to get the user's location coordinates every 15 minutes roughly and post it to the server. It is necessary to post data roughly at these intervals.
Implementation - I've made a sync adapter instead of using AlarmManager as it saves battery. I've set ContentResolver.addPeriodicSync() to sync my app every 15 minutes roughly which gets the current location and posts to server.
Problem - In case there's no internet connection, I want to continue taking the user's location every 15 minutes and save them in the local sqlite database. When the internet comes back again next time then I'll post all the saved locations in one go so that server data remains consistent and after that sync will resume as normal.
The main problem is that when there's no internet then the sync stops and I stop getting periodic sync callbacks in my app and I'm not able to save data in the local database. So what I want is that even when there's no internet I keep getting callbacks at regular intervals till the internet comes back and auto sync starts again. Can the sync adapter do that?
One solution I can think of is that I get a broadcast when the Internet stops and at that moment I start using the AlarmManager to start a service every 15 minutes and get the location and save to local database. And when the internet comes back on then I stop using the AlarmManager and go back to auto syncing.
Solution 2 - Provided by David Medenjak below. It is also efficient due to AlarmManager's setInexactRepeating() behavior which tries to imitate Sync adapter's behavior by scheduling Alarms for different apps together to reduce the number of times the CPU wakes up. Also it leads to a little simpler implementation. Would this the better way than the previous solution comparing the pros and cons?
Still any better way to achieve this?
You are mixing two things:
Getting the user location every 15 minutes
Syncing the data with the server
If you start mixing those you have a service and sync adapter that are both strongly dependent on each other, you have to check for states which of those has run and which should run. You might end up with the exact thing that you want (syncing every 15 minutes, just cache it if user is offline) but it will be hard to test and maintain.
Always use a service that is run every 15 minutes to store the current user location.
Periodically sync all updates to the server. This may also happen to be every 15 minutes, but you should not depend on this.
By having one part just storing the location and the other part just synchronizing the data you will have a much easier time handling things. And you also don't have to worry about internet connection or the interval of the synchronizations (since sync adapters are not guaranteed to run at exact times).
Concerning battery life (comments)
There should be no big difference whether a SyncAdapter uses gps and posts it immediately or a service persists it for the time being until the adapter syncs it. As soon as a task has to run every x minutes the device will have to wake up.
There might be slight improvements if the synchronization is run at a slower rate compared to the service, since the gps alone might not need any internet connection.
IntentService - runs every 15 min (using AlarmManager) and saves the user location in the db and mark it as unsent.
SyncAdapter - runs every 15 min and ties to send all unsent locations to the server. On success mark the location as sent. Android will make sure it's only run when there is a internet connection.
Edit:
The key point is separating the two sub-tasks (also suggested by #David Medenjak):
1) Get a location update and store it in a db
2) Send the location updates to the server when there is a network connection.
The FusedLocationProvider has a method
requestLocationUpdates (GoogleApiClient client, LocationRequest request, PendingIntent callbackIntent)
for when your app is in the background. Link
This method is suited for the background use cases, more specifically
for receiving location updates, even when the app has been killed by
the system.
You can use a LocationRequest to set the priority, interval, power consumption. Link
When you receive the pending intent, you can insert the location in the database and request a sync using the sync adapter.

Android SyncAdapter frequency

I'm trying to build a SyncAdapter that should be run every few seconds to quick check for specific data.
I'm using following code to run SyncAdapter
ContentResolver.setIsSyncable(account, CONTENT_AUTHORITY, 1);
ContentResolver.setSyncAutomatically(account, CONTENT_AUTHORITY, true);
and the sync is trigerred correclty however I noticed that it's call only once every ten minutes.
Based on android training http://developer.android.com/training/sync-adapters/running-sync-adapter.html saying:
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 calling setSyncAutomatically(), you can run the sync adapter
whenever the ContentResolver receives the message.
The network connection i available however it's only 3G connection, not WiFi - is this a reason on long wait?
How can I make my adapter to run every 10 seconds?
I already tried using periodic sync
ContentResolver.addPeriodicSync(account, CONTENT_AUTHORITY, new Bundle(), 10);
but it's still running every 10 minutes.
You are victim of the android SyncManager. Consider this scenario: you add a every 10 minutes sync for your application, but you have installed my application on your phone which as well asks the manager for a 10 minutes periodic sync and my application did the schedule requirement one second before than yours :) The sync operation of my application involves a transfer of big loads of data, hence using the whole ( or almost ) bandwidth of the available internet connection. The smart SyncManager will see that is not wise to start another sync process until the previous is done, and so it will wait the process to end, and only afterwards fire your sync.

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

Categories

Resources