RETO MEIER: Hi.
My name is Reto Meier, and I'm the tech lead for Google's
Android Developer Relations team.
This Efficient Data Transfers DevByte focuses on how you can
use prefetching to implement the big cookie model of
efficient data transfers and improve your app's user
experience by reducing latency and improving battery life.
In previous Efficient Data Transfers DevBytes, I looked
at the cell radio state machine and examined ways to
analyze apps' data transfer profiles to uncover
anti-patterns, such as these short, intermittent peaks and
consecutive but non-overlapping downloads.
Having discovered potential problems in this episode, I'll
look at how to use prefetching to solve some of them.
We know that carriers adjust the timings of cell radio
state machine to compromise between data transfer latency
and battery drain.
Within our app, we want to create the perfect user
experience that actually sits here, at the sweet spot of
nonexistent latency and virtually no battery impact.
Is such a thing even possible?
Yes, it is.
Prefetching offers the best of both worlds, an effective way
to reduce battery trained by reducing the number of data
transfers and minimizing the in-app latency triggered when
users have to wait for downloads to complete before
performing an action or viewing data.
By downloading all of the data that they're likely to need
for the current session in a single burst, over a single
connection at full capacity, you can significantly reduce
the number of radio activations.
Your challenge is figuring out what you need to download
ahead of time so that you can present the user with exactly
what they need, when they need it, without simply downloading
everything and wasting battery power and bandwidth,
downloading data that's never used.
How aggressively you prefetch depends on the size of the
data being downloaded and the likelihood of it being used.
As a rough guide for a typical cell radio connected over 3G,
you can prefetch around six seconds.
That's about one to two megs worth of data, if that data
has a 50% chance of being used within the current session.
Now at that point, the likely cost of downloading any unused
data matches the substantial savings lost by not
downloading that data to begin with.
But of course, not every network transfers
data at the same rate.
So the equation will change based on the speed of
transfers and the efficiency of the cell radio technology.
This code sets a default value for 3G networks and increases
or decreases the size of the prefetch cache based on the
speed and cost of each network.
Note that we want to prefetch significantly more data on
faster 4G networks.
And that's both to account for the larger amount of data that
can be downloaded within the same time period, but also
because of the higher battery cost
associated with these radios.
So that makes it even more important that we avoid extra
straight transitions whenever we can.
But if networks are different, so are our apps.
And as the likelihood of the data being used increases, so
does the amount of data it's worth prefetching.
So it's important to understand both what data your
users are likely to use and what your app's average
session length is.
A good starting point is to expect session lengths of
around two to five minutes.
So large downloads, such as video files, should be
downloaded in chunks at regular intervals.
Using the same logic for a music player, it would be good
practice to maintain a buffer of one song, in addition to
the one already being played, rather than prefetching, say,
a whole album or playlist.
This mitigates the risk of wasting significant bandwidth
and battery life if your user stops listening halfway
through a song.
You can take a similar approach when streaming by
using HTTP live streaming, which transmits audio in
bursts, rather than maintaining a continuous
stream that will keep the radio constantly active.
If we look at something like a news app, things get a little
more complicated.
A typical use case for a music player is to find an album or
playlist, hit play, and then listen.
So you have a really good chance of knowing what's
coming next and a reasonable expectation for what your user
is going to do.
And they're going to have a reasonable expectation that
there's going to be a start-up latency if they
select a new track.
Now in contrast, the browsing path when you're using
something like a news reader is much less predictable, with
readers potentially swiping between dozens of different
categories and jumping between hundreds of articles.
In many ways, a news reader behaves a lot like a browser,
so your approach to prefetching can utilize some
of the same techniques browsers
use to improve speeds.
Now, the most naive solution is to download the headlines
for a category once it's been selected and download the
related thumbnails when they scroll into view, and then
each of the articles once it's been clicked.
Now this forces the radio to remain active for basically
the entire session as you're switching categories,
scrolling headlines, and reading different articles.
And as a bonus, each click is going to have that latency
associated with it, as it has to download and process and
present the data that you've selected.
A much better approach is prefetching a reasonable
amount of data at start-up and prioritizing the first set of
news headlines and thumbnails which are likely to be
displayed, and continuing on with the remaining headlines
and thumbnails and also, article text.
Now it's also important to ensure that your prefetching
doesn't delay the app start-up by making the app wait for the
prefetch to complete fully before
letting the user interact.
The worst example of this is apps that display a splash
screen until a ping or download or
process cycle is completed.
So be sure to process all of your downloads in the
background, using IntentServices and libraries
like Volley to help process that data progressively and
concurrently and therefore minimize
that start-up latency.
The real trick is being able to accurately guess which
articles each user is likely to read and
prefetch only those.
Now one approach is a breadth-first search, assuming
that users are more likely to read articles
via the current one.
Or you can go depth-first and bank on them reading lots of
articles on a given topic.
Now a better approach is to use science.
Measure how your content is actually being consumed, and
use that to inform your decisions dynamically and on a
per-user basis.
You can start by keeping track of the metadata associated
with the content read by each user, such as the categories
or authors.
You can widen the scope further by incorporating the
content that their friends use by integrating a social graph
like Google+.
And finally, you can aggregate all of this information
together across all of your users and discover what's
being used by a lot of people.
Now taken together, you can form a sophisticated picture
of typical and expected usage patterns of your app,
customized for each user.
And that allows you to make an intelligent choice over which
data you should prefetch.
If that all sounds like a bit too much work, then for some
apps, it may even make sense to simply prefetch everything,
effectively providing support for a fully offline
experience.
This can be really effective for apps with data that
doesn't change very often, such as magazines.
But you risk spending significant bandwidth and
battery life downloading content that's never used, so
it should be done with caution.
One way to mitigate that cost associated with downloading
more than five minutes worth of data is to schedule the
download to occur at a time when bandwidth and battery
life isn't important, specifically when the device
is charging and connected to Wi-Fi, as determined here by
using the Connectivity Manager and Battery Manager.
And as long as you're monitoring the device state,
why not take it a step further and track the current activity
of the user?
Using the new location-based services Activity Recognition
API, you can modify the aggressiveness of your
prefetching based on what the user is doing.
In this example, we're going to increase the amount that we
prefetch when the user has the app open and standing still,
the assumption being that they're more likely to be
browsing articles while stationary than if they're
riding a bike.
Now so far, we've talked about the case where the app is
active in the foreground, but it's worth considering the
background case too.
Should we prefetch data when the app is in the background?
Well, generally speaking, it's better practice to delay your
downloads until the app is open, though this will
increase your start-up latency.
A good approach is to use Google Cloud Messaging to
notify your app of updates available on the server.
That eliminates the need to poll, to check for updates,
and provides a channel to notify your app of exactly
what it should be requesting at start-up.
Typically, you'll use GCM as a way to initiate client-side
notifications without the app needing to
initiate a separate download.
But where it makes sense, you can use this same mechanism to
trigger background updates.
I'll cover GCM in more detail, as well as topics including
batching, bundling transfers, and using SyncAdapters, in
future episodes of the Efficient Data Transfers
DevBytes series.