RETO MEIER: Hi, my name is Reto Meier.
I'm the tech lead for Google's Android
Developer Relations team.
Every time your app initiates a data connection, no matter
how much data it transfers, you cause a typical 3G
wireless radio to continue drawing power for upwards of
20 seconds.
This efficient data transfer's DevByte will look at how you
can use tools like SyncAdapters to batch and
bundle your data transfers, effectively phase shifting
them to produce a highly efficient, defragmented
network traffic profile like this one.
The underlying philosophy is to reduce the number of
transfer sessions by designing for a small
number of large downloads.
We can do that by batching up all of our delay tolerant
transfers and bundling them together with time sensitive
ones, potentially even preempting
planned future transfers.
For example, if your app uses analytics, rather than
uploading data as it's collected you can bundle the
results together queuing them for later upload when your app
performs a time-sensitive download.
Similarly, any time sensitive transfers should also preempt
updates or prefetching scheduled to
occur in the near future.
In each case you're attempting to mitigate the cost of the
transfer either with a delay tolerant upload, or a planned
future download by having them piggyback on the time
sensitive ones.
So now let's look at some techniques that you can use to
implement these best practices.
You can start by creating a batch queue to offset those
delay tolerant uploads.
This code snippet shows a simple queue to which you can
add either upload or download transfers that should occur in
the future.
Then, whenever you execute a time sensitive on-demand
download, or the next periodic update or prefetch is
initiated, you can also execute the transfer stored in
your queue.
This simple approach can be effective but, implemented
naively, it introduces the risk of transfers being lost
if your app is closed before the queue is cleared.
That's fine if your queue transfers are only relevant to
the current session, but for things like analytics or user
created data, you want to make sure that
you never lose anything.
The best solution is using a content provider.
This skeleton implementation shows the basic query, insert,
delete, and update methods that you
would need to implement.
A content provider can be backed by any data store, but
most commonly an SQLite database.
Now, rather than implementing a batch queue, you can insert
data into your content provider.
And then when your next transfer begins, query it for
pending transfers and remove successfully transmitted data
from that queue.
In this example I'm querying four queue check-ins, sending
them to a server and removing the successfully completed
ones from the queue.
I still need a technique to bundle these delay tolerant
transfers for the time sensitive ones, or possibly
also reschedule those transfers periodically to
ensure that they do eventually happen.
Most simply, I can revisit the earlier approach of creating a
series of methods to execute each transfer and then ensure
that each calls the other.
A much neater alternative is to use a SyncAdapter.
That's going to execute each of our transfers within its on
perform sync method.
It's important to note that each of the methods called
should contain its own logic for determining whether or not
it should actually perform an update, potentially skipping
transfer sessions if the radio hasn't already been activated
by a higher priority method.
Using a SyncAdapter to manage your transfers has a number of
advantages, including making it easy to centralize all of
your app's data transfers in one place so they all run at
the same time.
Your data transfers can also be scheduled with transfers
from other apps, all working towards the goal of reducing
the number of times the system has to switch on the radio.
SyncAdapter also implements data transfer best practices
for you, including checking for network connectivity,
retrying downloads when connectivity returns, and
automated batching and time shifting on requests that
occur within the same time window.
Creating a SyncAdapter is actually pretty
straightforward.
Start by extending the abstract threaded SyncAdapter
class and implementing your own data transfer code within
the onPerformSync handler as you can see here.
When executing the SyncAdapter, we'll spawn a
thread to invoke sync operations, so you don't need
to worry about moving your code into a background thread.
Similarly, multiple executions will be rejected so you can
write your transfer code knowing that it won't be run
concurrently with itself.
In order to run, the SyncAdapter must be bound to a
service, so you also need to create a service that
instantiates your SyncAdapter and binds to it.
SyncAdapters were originally designed to support
synchronization of a content provider
associated with an account.
For example, Gmail.
As a result, you also need to associate each SyncAdapter
with an account type and a content provider authority.
If you don't use accounts for your application, you can
create your own mock account by extending the abstract
account authenticator.
In this boilerplate, you'll notice that I'm overriding the
add account method.
Here you would typically ask the user to enter their
authentication details for your service.
But instead, you can just create a new stub account and
explicitly add it to the Account Manager.
Take particular note of the account type you specify.
Like the SyncAdapter, the account authenticator also
needs a service from which to run, so create a new service
that binds to your implementation.
Then create a metadata file for your account
authenticator.
Note in particular the account type, whic must match the one
which you provided earlier in your implementation.
The SyncAdapter also requires a content provider.
As we described earlier, a content provider is an
effective and efficient way to store the data that you're
planning to transfer.
But if you prefer not to use one you can create a stubbed
out implementation like this.
Next, create the metadata file for the SyncAdapter that will
be used to associate a content provider and
account type with it.
Note that here I am marking the SyncAdapter as not user
visible, so users won't be able to disable syncing from
their global settings.
Finally, add the new services and content providers to the
application manifest, associating the SyncAdapter
metadata with the SyncAdapter service, the account
authenticator, metadata with the account
authenticator service.
With your SyncAdapter complete you can now start thinking
about how to trigger your synchronizations.
And you can configure your SyncAdapter to run when server
data changes, when the data on the device changes, when the
framework sends a network Tcl, or at predetermined intervals,
or even on demand.
To be as efficient as possible, it's best practice
to rely primarily on server initiated synchronizations,
and where possible, avoid starting syncs as the direct
result of a user action.
Basically, try and eliminate the Refresh button.
The hardest part of the process is determining which
transfers are delay tolerant and which will require a sync
to be initiated.
Now, this flow chart gives an indication of the most common
initiators for possible syncs.
And I'll go through each flow now demonstrating how to
trigger a SyncAdapter in each case.
The best approach is to rely on server pings notifying your
app that there is new data to download.
Using Google Cloud Messaging-- which I'll explore in more
detail in another DevByte--
you can notify each installed instance of your app that it
needs to sync with the server.
Simply listen for an incoming GCM message notifying you of
new data becoming available, and call requests syncs
specifying the appropriate account and authority.
This eliminates the need for any client side polling while
maintaining constantly up-to-date client data.
Conversely, you may want to synchronize changes on the
client side to the Cloud.
Now, this could be anything from analytics data to new
emails, so you want to be careful when deciding which
changes are sufficient to initiate an actual transfer.
If you're using content providers, you can configure
your SyncAdapter to trigger whenever the content provider
changes by setting the supports uploading
attribute to true.
Which happens to be the default.
Now, when this is set, an upload only sync will be
requested whenever your content provider executes a
notify change call with the sync to network parameter set
to true as shown here.
Now within the onPerformSync handler you can detect these
upload only syncs by examining the sync extras upload extra.
This can be useful if you want to rate limit
client initiated transfers.
You can also configure your SyncAdapter to sync whenever
the platform opens a connection to keep the TCP/IP
connection used for things like Google Cloud Messaging or
Live by setting it to sync automatically.
Now this approach ensures that your app is regularly updated
without having to schedule your own client side polling.
Now that said, the frequency of network Tcls can lead to a
large number of transfers, so this should only be enabled
when your app is in the foreground.
And even then, you may want to artificially lower the
frequency of syncs within your onPerformSync method.
Now, if you want to perform regular syncs at a known
frequency, you can use the addPeriodicSync method.
While you can set any frequency, this approach is
more typically used to ensure at least one sync happens
every 12 to 24 hours when the app isn't already running.
Typically with client and server changes initiating most
of those updates, particularly when the app is in the
foreground.
Now finally, if you need to initiate a time sensitive
transfer, either when the app launches, when the user
browses something that you've not prefetched, or just to
prefetch additional data, you can call requestSync.
In cases where an update must occur immediately, in order to
avoid impacting the user experience, you can force a
manual expedited sync by setting the manual and
expedited flags within the settings bundle.
These settings will ignore any back offs and sync settings
that might delay the sync in attempt to execute it
immediately instead.
Now you can find all the details to creating and using
SyncAdapters in this Android training class.
So, having learned how to perform our updates
efficiently, other DevBytes will look at ways to adjust
our update frequency, reducing data payload sizes, and using
Google Cloud Messaging in more detail.