8.3 Efficient data transfer


Transferring data is an essential part of most Android apps, but it can affect battery life and increase data usage. Using the wireless radio to transfer data is potentially one of your app's most significant sources of battery drain.

Users care about battery drain because they would rather use their mobile device without it connected to the charger. And users care about data usage, because every bit of data transferred can cost them money.

In this chapter, you learn how your app's networking activity affects the device's radio hardware so you can minimize the battery drain associated with network activity. You also learn how to wait for the proper conditions to accomplish resource-intensive tasks.

Wireless radio state

A fully active wireless radio consumes significant power. To conserve power when not in use, the radio transitions between different energy states. However, there is a trade-off between conserving power and the time it takes to power up when needed.

For a typical 3G network the radio has these three energy states:

  • Full power: Used when a connection is active. Allows the device to transfer data at its highest possible rate.
  • Low power: An intermediate state that uses about 50% less battery.
  • Standby: The minimal energy state, during which no network connection is active or required.

While the low and standby states use much less battery, they also introduce latency to network requests. Returning to full power from the low state takes around 1.5 seconds, while moving from standby to full can take over 2 seconds.

Android uses a state machine to determine how to transition between states. To minimize latency, the state machine waits a short time before it transitions to lower energy states.  Radio transitioning for the 3G wireless state machine, noborder

The radio state machine on each device, particularly the transition delay ("tail time") and startup latency, vary based on the wireless radio technology employed, for example 2G, 3G, or LTE. The state machine is defined and configured by the carrier network over which the device is operating.

This chapter describes a representative state machine for a typical 3G wireless radio. However, the general principles and resulting best practices are applicable for all wireless radio implementations.

As with any best practices, there are trade-offs that you need to consider for your own app development.

Bundling network transfers

Every time you create a new network connection, the radio transitions to the full power state. In the case of the 3G radio state machine described above, the radio remains at full power for the duration of your transfer. Then there are 5 seconds of tail time, followed by 12 seconds at the low energy state. Then the radio turns off. For a typical 3G device, every data transfer session causes the radio to draw power for almost 20 seconds.

What this means in practice:

  • An app that transfers unbundled data for 1 second every 18 seconds keeps the wireless radio always active.
  • An app that transfers bundled data for 3 seconds every minute keeps the radio in the high power state for only 8 seconds. Then the radio is in the low power state for an additional 12 seconds.

The second example allows the radio to be idle for 40 seconds out of every minute, resulting in a massive reduction in battery consumption.  Relative wireless radio power use for bundled versus unbundled transfers

It's important to bundle and queue up your data transfers. You can bundle transfers that are due to occur within a time window and make them all happen simultaneously, ensuring that the radio draws power for as little time as possible.


To prefetch data means that your app takes a guess at what content or data the user will want next, and fetches the data ahead of time. For example, when the user looks at the first part of an article, a good guess is to prefetch the next part. If a user is watching a video, fetching the next minutes of the video is also a good guess.

Prefetching allows you to download all the data you are likely to need for a given time period in a single burst, over a single connection, at full capacity. This reduces the number of radio activations required to download the data. As a result, you conserve battery life, improve latency for the user, lower the required bandwidth, and reduce download times.

Prefetching has trade-offs. If you download too much data or the wrong data, you might increase the drain on the battery. If you download at the wrong time, users might end up waiting. Optimizing prefetching data is an advanced topic not covered in this course, but the following guidelines cover common situations:

  • How aggressively you prefetch data depends on the size of the data being downloaded and the likelihood of the data being used. Here's a rough guide, based on the state machine described above: for data that has a 50% chance of being used within the current user session, you can typically prefetch for around 6 seconds (approximately 1-2 MB). After this point, the potential cost of downloading unused data matches the potential savings of not downloading that data to begin with.
  • It's good practice to prefetch data in such a way that you only need to initiate downloads of 1-5 MB every 2-5 minutes. For example, for a large video file, you would download a chunk of the data every 2-5 minutes, effectively prefetching only the video data likely to be viewed in the next few minutes.

Prefetching example

Many news apps attempt to reduce bandwidth by downloading headlines only after a category has been selected, full articles only when the user wants to read them, and thumbnails just as they scroll into view.

Using this approach, the radio is forced to remain active for the majority of a news-reading session as users scroll headlines, change categories, and read articles. Not only that, but the constant switching between energy states results in significant latency when switching categories or reading articles.

Here's a better approach:

  1. Prefetch a reasonable amount of data at startup, beginning with the first set of news headlines and thumbnails. This ensures a quick startup time.
  2. Continue with the remaining headlines, the remaining thumbnails, and the article text for each article from the first set of headlines.

Monitor connectivity state

Devices can network using different types of hardware:

  • Wireless radios use varying amounts of battery depending on technology, and higher bandwidth consumes more energy. Higher bandwidth means you can prefetch more aggressively, downloading more data during the same amount of time. However, because the tail-time battery cost is relatively higher, it's also more efficient to keep the radio active for longer periods during each transfer session to reduce the frequency of updates.
  • Wi-Fi radio uses significantly less battery than wireless and offers greater bandwidth.
Note: Perform data transfers when connected over Wi-Fi whenever possible.

You can use the ConnectivityManager to determine the active wireless radio and modify your prefetching routines depending on network type:

ConnectivityManager cm =
TelephonyManager tm =

NetworkInfo activeNetwork = cm.getActiveNetworkInfo();
int PrefetchCacheSize = DEFAULT_PREFETCH_CACHE;

switch (activeNetwork.getType()) {
    case (ConnectivityManager.TYPE_WIFI):
        PrefetchCacheSize = MAX_PREFETCH_CACHE; 
    case (ConnectivityManager.TYPE_MOBILE): {
        switch (tm.getNetworkType()) {
           case (TelephonyManager.NETWORK_TYPE_LTE |
                PrefetchCacheSize *= 4;
            case (TelephonyManager.NETWORK_TYPE_EDGE |
               PrefetchCacheSize /= 2;
            default: break;
  default: break;

The system sends out broadcast intents when the connectivity state changes, so you can listen for these changes using a BroadcastReceiver.

Monitor battery state

To minimize battery drain, monitor the state of your battery and wait for specific conditions before initiating a battery-intensive operation.

The BatteryManager broadcasts all battery and charging details in a broadcast Intent that includes the charging status.

To check the current battery status, examine the broadcast intent:

IntentFilter ifilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
Intent batteryStatus = context.registerReceiver(null, ifilter);
// Are we charging / charged?
int status = batteryStatus.getIntExtra(BatteryManager.EXTRA_STATUS, -1);
boolean isCharging = status == BatteryManager.BATTERY_STATUS_CHARGING ||
                     status == BatteryManager.BATTERY_STATUS_FULL;

// How are we charging?
int chargePlug = batteryStatus.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1);
boolean usbCharge = chargePlug == BatteryManager.BATTERY_PLUGGED_USB;
boolean acCharge = chargePlug == BatteryManager.BATTERY_PLUGGED_AC;

If you want to react to changes in the battery charging state, use a BroadcastReceiver to register for the battery status actions in your code:

public void registerBatteryChargingStateReceiver()

     // Create Receiver Object
     BroadcastReceiver receiver = new MyPowerReceiver();

     //Create Intent Filter
     IntentFilter intentFilter = new IntentFilter();


     // Register broadcast receiver
     this.registerReceiver(receiver, intentFilter);


Broadcast intents are also delivered when the battery level changes in a significant way:

Broadcast action Constant value
Intent.ACTION_BATTERY_LOW "android.intent.action.BATTERY_LOW"
Intent.ACTION_BATTERY_OKAY "android.intent.action.BATTERY_OKAY"


Constantly monitoring the connectivity and battery status of the device can be a challenge. It requires using components such as broadcast receivers, which can consume system resources even when your app isn't running. Because transferring data efficiently is such a common task, the Android SDK provides a class that makes efficient data transfer much easier: JobScheduler.

Introduced in API level 21, JobScheduler allows you to schedule a task around specific conditions, rather than a specific time as with AlarmManager.

JobScheduler has three components:

  • JobInfo uses the builder pattern to set the conditions for the task.
  • JobService is a wrapper around the Service class where the task is actually completed.
  • JobScheduler schedules and cancels tasks.
Note: JobScheduler is only available on devices running API 21+, and is not available in the support library. If your app targets devices with earlier API levels, look into the backwards compatible WorkManager, a new API currently in alpha, that allows you to schedule background tasks that need guaranteed completion (regardless of whether the app process is around or not). WorkManager provides JobScheduler-like capabilities to API 14+ devices, even those without Google Play Services.


Set the job conditions by constructing a JobInfo object using the JobInfo.Builder class. The JobInfo.Builder class is instantiated from a constructor that takes two arguments: a job ID (which can be used to cancel the job), and the ComponentName of the JobService that contains the task. Your JobInfo.Builder must set at least one, non-default condition for the job. For example:

JobScheduler scheduler = (JobScheduler) getSystemService(JOB_SCHEDULER_SERVICE);
ComponentName serviceName = new ComponentName(getPackageName(),
JobInfo.Builder builder = new JobInfo.Builder(JOB_ID, serviceName);
JobInfo jobInfo = builder.build();
Note: See the related practical for a complete example.

The JobInfo.Builder class has many set() methods that allow you to determine the conditions of the task. Below is a list of few available constraints with their respective set() methods and class constants:

  • Minimum Latency: The minimum amount of time to wait before completing the task. Set this condition using the setMinimumLatency() method, which takes a single argument: the amount of time to wait in milliseconds.
  • Override Deadline: The maximum time to wait before running the task, even if other conditions aren't met. Set this condition using the setOverrideDeadline() method, which is the maximum time to wait in milliseconds.
  • Periodic: Repeats the task after a certain amount of time. Set this condition using the setPeriodic() method, passing in the repetition interval. This condition is mutually exclusive with the minimum-latency and override-deadline conditions: setting setPeriodic() with one of them results in an error.
  • Required Network Type: The kind of network type your job needs. If the network isn't necessary, you don't need to call this function, because the default is NETWORK_TYPE_NONE. Set this condition using the setRequiredNetworkType() method, passing in one of the following constants: NETWORK_TYPE_NONE, NETWORK_TYPE_ANY, NETWORK_TYPE_NOT_ROAMING, NETWORK_TYPE_UNMETERED.
  • Required Charging State: Whether the device needs to be plugged in to run this job. Set this condition using the setRequiresCharging() method, passing in a boolean. The default is false.
  • Requires Device Idle: Whether the device needs to be in idle mode to run this job. "Idle mode" means that the device isn't in use and hasn't been for some time, as loosely defined by the system. When the device is in idle mode, it's a good time to perform resource-heavy jobs. Set this condition using the setRequiresDeviceIdle()method, passing in a boolean. The default is false.


Once the conditions for a task are met, the framework launches a subclass of JobService, which is where you implement the task itself. The JobService runs on the UI thread, so you need to offload blocking operations to a worker thread.

Declare the JobService subclass in the Android Manifest, and include the BIND_JOB_SERVICE permission:

<service android:name="MyJobService"
        android:permission="android.permission.BIND_JOB_SERVICE" >

In your subclass of JobService, override two methods, onStartJob() and onStopJob().


The system calls onStartJob() and automatically passes in a JobParameters object, which the system creates with information about your job. If your task contains long-running operations, offload the work onto a separate thread. The onStartJob() method returns a boolean: true if your task has been offloaded to a separate thread (meaning it might not be completed yet) and false if there is no more work to be done.

Use the jobFinished() method from any thread to tell the system that your task is complete. This method takes two parameters: the JobParameters object that contains information about the task, and a boolean that indicates whether the task needs to be rescheduled, according to the defined backoff policy.


If the system determines that your app must stop execution of the job, even before jobFinished() is called, the system calls onStopJob(). This happens if the requirements that you specified when you scheduled the job are no longer met.


  • If your app requests Wi-Fi with setRequiredNetworkType() but the user turns off Wi-Fi while your job is executing, the system calls onStopJob().
  • If your app specifies setRequiresDeviceIdle() but the user starts interacting with the device while your job is executing, the system calls onStopJob().

You're responsible for how your app behaves when it receives onStopJob(), so don't ignore it. This method returns a boolean, indicating whether you'd like to reschedule the job based on the defined backoff policy, or drop the task.


The final part of scheduling a task is to use the JobScheduler class to schedule the job. To obtain an instance of this class, call getSystemService(JOB_SCHEDULER_SERVICE). Then schedule a job using the schedule() method, passing in the JobInfo object you created with the JobInfo.Builder. For example:


The framework is intelligent about when you receive callbacks, and it attempts to batch and defer them as much as possible. If you don't specify a deadline on your job, the system can run the job at any time, depending on the current state of the JobScheduler object's internal queue. However, the job might be deferred as long as until the next time the device is connected to a power source.

To cancel a job, call cancel(), passing in the job ID from the JobInfo.Builder object, or call cancelAll(). For example:


The related practical is 8.3: JobScheduler.

Learn more

Android developer documentation:


results matching ""

    No results matching ""