androidengineers.Book a session

Battery & Network Optimization

Doze, App Standby & Background Limits

article25 minHard

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:

BucketCriteriaJobs/dayAlarms/hour
ActiveUsed recentlyUnrestrictedUnrestricted
Working SetUsed in past day10 / 2hrs10
FrequentUsed in past week5 / 8hrs5
RareUsed in past month1 / 24hrs1
RestrictedFlagged by systemRareVery 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

FeatureImpactMitigation
DozeNetwork and jobs blocked between windowsUse FCM for real-time, WorkManager for deferrable
App StandbyJob frequency limited by bucketDon't rely on periodic work for user-facing features
Background ServiceKilled seconds after app goes backgroundUse Foreground Service (with notification) or WorkManager
Implicit broadcastsNot delivered when app in backgroundUse dynamic receiver in foreground; WorkManager for constraints

YOUR LEARNING JOURNEY

0 of 177 available lessons completed

Progress saved in this browser. No account needed.
Doze, App Standby & Background Limits | Android System Design | Android Engineers