Android's battery-saving features have become progressively more restrictive with each major release. Understanding exactly what's allowed — and when — is essential for any app that does background work.
Doze Mode
Triggers: Device unplugged + screen off + stationary for ~30–60 minutes.
Effect: System enters maintenance windows periodically. Between windows:
- Network access blocked
- Wake locks ignored
- Alarms (AlarmManager) deferred
- JobScheduler and WorkManager jobs deferred
What still works in Doze:
- FCM high-priority push messages
- Phone calls, SMS
- Explicit user activity (unlocking phone)
// Test your app in Doze:
adb shell dumpsys deviceidle force-idle
// Run your scenario, then check if background work triggered
adb shell dumpsys deviceidle step // cycle through maintenance windows
adb shell dumpsys deviceidle unforce // restore normal behavior
App Standby
Apps that haven't been used recently are put in a standby bucket. The bucket determines how often background work can run:
| Bucket | Criteria | Jobs/day | Alarms/hour |
|---|---|---|---|
| Active | Used recently | Unrestricted | Unrestricted |
| Working Set | Used in past day | 10 / 2hrs | 10 |
| Frequent | Used in past week | 5 / 8hrs | 5 |
| Rare | Used in past month | 1 / 24hrs | 1 |
| Restricted | Flagged by system | Rare | Very limited |
Check your app's bucket:
adb shell am get-standby-bucket com.example.myapp
Android 8+ Background Execution Limits
Background Service restrictions:
// ❌ Won't work when app in background (Android 8+)
startService(Intent(this, MyService::class.java))
// ✅ Use WorkManager for deferrable work
WorkManager.getInstance(this).enqueue(
OneTimeWorkRequestBuilder<MyWorker>()
.setConstraints(Constraints.Builder().setRequiredNetworkType(NetworkType.CONNECTED).build())
.build()
)
// ✅ Use startForegroundService for immediate user-visible work
val intent = Intent(this, ForegroundService::class.java)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
startForegroundService(intent)
} else {
startService(intent)
}
Foreground Services: Required Types (Android 14)
As of Android 14, foregroundServiceType is mandatory:
<service
android:name=".UploadService"
android:foregroundServiceType="dataSync" <!-- required -->
android:exported="false" />
Available types: camera, connectedDevice, dataSync, health, location, mediaPlayback, mediaProjection, microphone, phoneCall, remoteMessaging, shortService, specialUse, systemExempted.
class UploadService : Service() {
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
val notification = buildNotification()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
startForeground(NOTIFICATION_ID, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC)
} else {
startForeground(NOTIFICATION_ID, notification)
}
// ... do work
return START_NOT_STICKY
}
}
Broadcast Receiver Restrictions
Implicit broadcasts (background):
<!-- ❌ No longer delivered to background apps (Android 8+) -->
<receiver android:name=".ConnectivityReceiver">
<intent-filter>
<action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
</intent-filter>
</receiver>
// ✅ Register dynamically (in foreground only)
class MainActivity : AppCompatActivity() {
private val connectivityReceiver = ConnectivityReceiver()
override fun onStart() {
super.onStart()
registerReceiver(connectivityReceiver, IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION))
}
override fun onStop() {
super.onStop()
unregisterReceiver(connectivityReceiver)
}
}
// ✅ Or use WorkManager with NetworkType constraint — handles this automatically
Key Takeaways
| Feature | Impact | Mitigation |
|---|---|---|
| Doze | Network and jobs blocked between windows | Use FCM for real-time, WorkManager for deferrable |
| App Standby | Job frequency limited by bucket | Don't rely on periodic work for user-facing features |
| Background Service | Killed seconds after app goes background | Use Foreground Service (with notification) or WorkManager |
| Implicit broadcasts | Not delivered when app in background | Use dynamic receiver in foreground; WorkManager for constraints |