How Android Saves Battery
The Battery Problem
Battery life is the most common user complaint on Android. Users uninstall apps that drain their battery. Android has aggressive power management features that restrict what your app can do when not in the foreground.
Doze Mode
Introduced in Android 6.0 (API 23), Doze mode restricts app activity when the device is:
- Unplugged
- Stationary (no movement for a while)
- Screen off
During Doze, the system:
- Defers network access
- Defers alarms (setExact() is deferred)
- Defers Wi-Fi scanning
- Batches JobScheduler work
- Defers syncs
The system periodically opens maintenance windows where apps can run briefly. The intervals grow longer the longer the device stays in Doze.
Doze timeline:
15 min → 30 min → 1 hour → 2 hours → 4 hours → 8+ hours
App Standby Buckets
Android 9.0 introduced App Standby Buckets, which categorize apps based on usage frequency:
| Bucket | Jobs | Alarms | Network | Jobs/Day |
|---|---|---|---|---|
| Active | Yes | Yes | Yes | Unlimited |
| Working Set | Yes (limited) | Yes (limited) | Yes (limited) | ~10 |
| Frequent | Yes (limited) | Yes (limited) | Yes (limited) | ~5 |
| Rare | Yes (very limited) | Yes (very limited) | Deferred | ~1 |
| Never | No | No | No | 0 |
Check your app's bucket:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
val bucket = usageStatsManager
.appStandbyBuckets
.firstOrNull { it.packageName == packageName }
Log.d("Battery", "App bucket: ${bucket?.bucket}")
}
PowerSave Mode
When the battery is critically low, PowerSave mode kicks in. It restricts:
- CPU performance
- Background network
- Location updates
- Vibration
- Animations
You can check if PowerSave is active:
val powerManager = getSystemService(PowerManager::class.java)
val isPowerSave = powerManager.isPowerSaveMode
Battery Usage API
Android provides APIs to understand your app's battery impact:
// Check if battery optimizations should be disabled
val intent = Intent(Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS)
startActivity(intent)
// Request exemption (use sparingly)
val intent = Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS)
intent.data = Uri.parse("package:$packageName")
startActivity(intent)
Only request battery optimization exemption if your app genuinely requires it (e.g., location tracking, music playback). Google Play reviews apps that request this exemption.
Efficient Alarms, Network, and Location
Efficient Alarms
Since Android 6.0, exact alarms are deferred during Doze. Use the right alarm type:
val alarmManager = getSystemService(AlarmManager::class.java)
// Inexact alarm - system batches with other alarms
alarmManager.set(
AlarmManager.ELAPSED_REALTIME_WAKEUP,
SystemClock.elapsedRealtime() + interval,
pendingIntent
)
// Exact alarm - only use when timing matters
// Requires SCHEDULE_EXACT_ALARM permission on Android 12+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
if (alarmManager.canScheduleExactAlarms()) {
alarmManager.setExactAndAllowWhileIdle(
AlarmManager.ELAPSED_REALTIME_WAKEUP,
SystemClock.elapsedRealtime() + interval,
pendingIntent
)
}
} else {
alarmManager.setExactAndAllowWhileIdle(
AlarmManager.ELAPSED_REALTIME_WAKEUP,
SystemClock.elapsedRealtime() + interval,
pendingIntent
)
}
Best practice: Use WorkManager instead of alarms for most scheduling needs. WorkManager handles Doze, App Standby, and battery constraints automatically.
Network Batching
Making many small network requests drains battery because each request requires the radio to wake up and establish a connection. Batch requests together:
// BAD: Multiple small requests
suspend fun syncData() {
fetchUserProfile()
fetchNotifications()
fetchMessages()
}
// GOOD: Single batched request
suspend fun syncData() {
val batchResponse = api.fetchBatch(
requests = listOf(
BatchRequest("profile"),
BatchRequest("notifications"),
BatchRequest("messages")
)
)
processProfile(batchResponse.profile)
processNotifications(batchResponse.notifications)
processMessages(batchResponse.messages)
}
Also use prefetching: when the user opens an app, fetch data they will likely need soon:
// Prefetch next page when user is on first page
lifecycleScope.launch {
pager.collectLatest { page ->
repository.fetchPage(page)
repository.prefetchPage(page + 1) // Prefetch next
}
}
Efficient Location Updates
Location is one of the biggest battery drains. Use the minimum accuracy and frequency needed:
// Use coarse location when fine is not needed
val locationRequest = LocationRequest.Builder(
Priority.PRIORITY_BALANCED_POWER_ACCURACY, // Not PRIORITY_HIGH_ACCURACY
30_000L // 30 second intervals, not continuous
).apply {
setMinUpdateDistanceMeters(100f) // Only report if moved 100m
setWaitForAccurateLocation(false)
}
// Stop updates when not needed
override fun onStop() {
super.onStop()
fusedLocationClient.removeLocationUpdates(callback)
}
Use cases for location precision:
- PRIORITY_HIGH_ACCURACY: Navigation, fitness tracking
- PRIORITY_BALANCED_POWER_ACCURACY: Nearby places, weather
- PRIORITY_LOW_POWER: City-level location
- PRIORITY_PASSIVE: Only use location from other apps
Foreground Services
If your app needs to run continuously (music, navigation), use a foreground service with a notification:
class MusicService : Service() {
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
val notification = NotificationCompat.Builder(this, CHANNEL_ID)
.setContentTitle("Playing Music")
.setContentText("Song Title")
.setSmallIcon(R.drawable.ic_music)
.build()
startForeground(1, notification)
return START_STICKY
}
override fun onBind(intent: Intent?): IBinder? = null
}
Foreground services are exempt from Doze restrictions but require a visible notification. Use them only when the user expects continuous background activity.
Battery Historian
Battery Historian is a tool that visualizes your app's battery usage:
# Install and run
java -jar battery-historian.jar
# Open http://localhost:9999 in browser
# Upload a bugreport or connect via ADB
adb bugreport > bugreport.zip
It shows a timeline of:
- Network activity
- Location requests
- Wake locks
- Alarms
- JobScheduler work
- Screen on/off
- Doze state
Use it to identify when your app wakes the device and optimize those wakeups.
Quiz
1. What happens to alarms set with setExact() during Doze mode?
2. Which App Standby Bucket allows unlimited job scheduling?
3. Why should you batch network requests instead of making many small requests?
4. Which location priority should you use for a weather app that needs city-level accuracy?
Flashcards
Question
What is Doze mode in Android?
Click to reveal answer
Answer
A power management feature that defers network, alarms, and background work when the device is unplugged, stationary, and screen-off. Apps can only run during brief maintenance windows.
Question
What are App Standby Buckets?
Click to reveal answer
Answer
A system that categorizes apps by usage frequency (Active, Working Set, Frequent, Rare, Never) and restricts jobs, alarms, and network access accordingly.
Question
Why does network usage drain battery?
Click to reveal answer
Answer
Each network request requires the radio to wake up from low-power mode, establish a connection, transmit data, and go back to sleep. The radio wake-up is the most expensive part.
Question
When should you use setExactAndAllowWhileIdle()?
Click to reveal answer
Answer
Only when an alarm must fire during Doze mode, such as for medication reminders or time-critical notifications. Use WorkManager for most scheduling needs instead.
Revision Notes
Key Takeaways
- 1. Doze mode defers network, alarms, and background work when the device is idle
- 2. App Standby Buckets restrict background activity based on usage frequency
- 3. Batch network requests to reduce radio wake-ups and save battery
- 4. Use the least precise location accuracy that meets your use case
- 5. Battery Historian visualizes your app's battery impact
Interview Tips
- • Explain Doze mode and when it activates
- • Discuss how App Standby Buckets affect your app's background work
- • Explain why batching network requests saves battery
- • Describe how to reduce location battery drain
- • Know when to use WorkManager vs alarms vs foreground services
- • Be ready to discuss battery optimization strategies for a specific app type
Cheat Sheet
Battery Optimization Cheat Sheet
Doze Mode (Android 6.0+):
- Triggers when: unplugged + stationary + screen off
- Defers: network, alarms, Wi-Fi, syncs
- Maintenance windows grow longer over time
- Use setExactAndAllowWhileIdle() for Doze-resistant alarms
App Standby Buckets (Android 9.0+):
- Active: Unlimited (user is interacting)
- Working Set: ~10 jobs/day
- Frequent: ~5 jobs/day
- Rare: ~1 job/day
- Never: No background work
Efficient Alarms:
- Use inexact alarms (alarmManager.set()) when possible
- Use WorkManager instead of alarms for most scheduling
- SCHEDULE_EXACT_ALARM permission required on Android 12+
Network Optimization:
- Batch multiple requests into one
- Prefetch data user will need soon
- Use connectivity-aware networking
Location Optimization:
- Use least precise accuracy needed
- Set minimum update distance
- Stop updates when not needed
- Consider PASSIVE provider when possible
Tools:
- Battery Historian: Visualize battery usage
- adb shell dumpsys batterystats
- Developer Options > Battery Usage