Zygote
Memory, lifecycle & powerintermediate

Doze and App Standby

Two different throttles for two different situations: Doze when the device is idle, App Standby when the app is unused. How they interact, why maintenance windows widen, and what still gets through.

12 minAPI 23–37 · 17interactivequizverified 2026-10-27

Android's battery problem was never the screen. It was hundreds of apps waking the device for a few seconds each, dozens of times an hour, to check whether anything had changed. Doze and App Standby are the two answers: one throttles the device, one throttles the app.

TL;DR#

  • Doze (API 23Android 6.0) restricts background work when the device is idle, unplugged, and stationary: no network, deferred jobs and alarms, ignored wakelocks — with periodic maintenance windows.
  • Doze on the go (API 24Android 7.0) applies the same restrictions when the device is moving, with a motion exception to wake it.
  • App Standby (API 23Android 6.0) defers an individual app's background work when the user has not used it.
  • App Standby Buckets (API 28Android 9) replace one global rule with tiers: active, working set, frequent, rare, restricted.
  • High-priority FCM messages and exact alarms with the right permission are the escape hatches — deliberately narrow.

Mental model#

Think of a hotel at night.

Doze is the hotel's night mode: the corridors dim, the lifts run less often, and deliveries are held at the desk until a scheduled window. It applies to every guest equally.

App Standby is the concierge's judgement about a guest: someone who has not left their room in two days probably does not need a wake-up call every hour.

Standby buckets are the concierge's tiers — a VIP, a regular, a rare visitor, and someone on the do-not-disturb list.

Where the analogy breaks#

A hotel's night mode has one schedule. Doze's maintenance windows widen over time: the longer the device stays idle, the less often it wakes, up to a maximum interval. The system is actively learning how little it can get away with, which no fixed hotel schedule does.

The big picture#

Loading diagram…
Text description of this diagram

A compressed four-day Doze timeline. The screen goes off at hour 3; after a stationary period light Doze begins at hour 4 and deep Doze at hour 8. Maintenance windows open at hours 5, 12, 26 and 54, with the gaps widening. Inside each window, deferred jobs and alarms run; outside, a high-priority push can still wake the device. At hour 96 the device wakes and the cycle restarts.

Look at the spacing of the maintenance windows. That widening is the whole mechanism: the system does not need a new rule for a device that has been idle for a day versus an hour — the interval is the rule.

Deep dive#

What Doze actually restricts#

When the device enters Doze:

  • Network access is suspended. The app cannot open sockets; a pending request fails or blocks until a window.
  • Jobs and alarms are deferred. JobScheduler jobs wait for a maintenance window; AlarmManager alarms are deferred unless they are exact and permitted.
  • Wakelocks are ignored. An app cannot keep the CPU awake.
  • Syncs and location are suspended in deep Doze.

Android 7.0 added Doze on the go: the restrictions apply even when the device is moving, which was a significant change for fitness and navigation apps. A significant-motion or location event exits Doze so the app can react.

1

The maintenance window#

A maintenance window is a short period during which deferred work is allowed to run: network is restored, jobs fire, alarms are delivered. Then the device returns to idle.

The windows are not on a fixed schedule. The first comes relatively soon after Doze begins; subsequent ones are spaced further apart, up to a maximum. This is the classic exponential-backoff shape, applied to a whole device.

For an app, the practical consequence is: do not assume your job runs at the time you scheduled it. A job scheduled for 03:00 may run at 05:00 or 11:00, depending on when the next window falls.

App Standby#

App Standby is per-app and based on usage. When the user has not interacted with an app for a while, the system defers its background network activity and its jobs until the app is used again or the device is plugged in.

The definition of "interaction" is deliberately broad: launching the app, receiving a notification the user taps, or a foreground service all count. Being launched by another app's intent may not.

The buckets#

API 28Android 9 replaced the single App Standby rule with five buckets, each with its own quotas.2

Loading diagram…
Text description of this diagram

A state machine of the five standby buckets. Active is entered when the app is used and demotes to working set after minutes, to frequent after days, and to rare after weeks. Restricted is applied by the system or the user, not by usage, and is the only state that removes background jobs, alarms and network entirely. Opening the app promotes it back to active from any bucket.

The bucket affects:

  • JobScheduler: how often jobs may run, and whether expedited jobs are available.
  • Alarms: how much an alarm may be deferred.
  • Network: whether the app gets network while the device is idle.
  • FCM: whether high-priority messages are delivered immediately.

The restricted bucket is not a usage tier. It is applied when an app is judged to be misbehaving, or when the user sets it in Settings, and it removes background execution almost entirely.

The escape hatches#

Two mechanisms deliberately bypass the restrictions:

High-priority FCM. A message sent with high priority wakes the device and is delivered immediately, even in Doze. This is how messaging apps stay responsive. It is also heavily rate-limited, because otherwise every app would use it for everything.

Exact alarms. AlarmManager.setExactAndAllowWhileIdle fires at an exact time even in Doze, but requires the SCHEDULE_EXACT_ALARM permission on API 31Android 12 and later, and is subject to policy restrictions on what it may be used for.3

Both are narrow on purpose. A general "run whenever I want" escape hatch would recreate the problem Doze exists to fix.

What App Standby is not#

App Standby does not stop the app from running when the user is using it. It does not prevent the app from receiving a broadcast that the system considers important. And it does not remove the app's notifications — a notification is how the user re-engages, which is exactly what the system wants.

Testing#

The device-idle state can be forced:

adb shell dumpsys deviceidle force-idle
adb shell dumpsys deviceidle unforce

and an app's bucket can be set directly:

adb shell am set-standby-bucket com.example.app rare

These are the fastest way to test whether an app's background work survives the real conditions, without waiting hours for the device to become idle.

What "deferred" actually means for a job#

A deferred job is not cancelled. It stays in the scheduler's queue with its constraints, and it runs when the constraints are met and the device is in a state that allows it.

That means an app should describe the conditions under which the work is valid, not the time at which it should happen. "Sync when on Wi-Fi and charging" is a job; "sync at 03:00" is an alarm, and a worse fit for the problem.

The failure mode is an app that schedules a job with no constraints and expects it to run immediately. It will run — eventually — and the developer will conclude that JobScheduler is broken.

Doze versus the app's own schedule#

Doze operates on the device; App Standby operates on the app. They compose: an app in the frequent bucket on a device in Doze gets both restrictions, and its job may wait until the next maintenance window and then also until its bucket's quota allows it.

This is why apps that need reliable timing use exact alarms rather than jobs: an alarm is the only mechanism designed to survive both throttles.

The interaction with push messaging#

A high-priority FCM message is the sanctioned way to wake a device. It is delivered even in Doze, and the app can then do a small amount of work.

It is rate-limited because the alternative — unlimited high-priority messages — would make Doze meaningless. An app that sends a high-priority message for every sync will find its messages deprioritised, which is a policy outcome rather than a technical one.

What changed in the bucket model#

Before buckets, App Standby was binary: an app was either active or idle. Buckets made it a gradient, which let the system be more generous with apps the user actually uses and more aggressive with apps they do not.

The practical effect for a well-behaved app is that its background work is restricted in proportion to how little the user cares about it — which is exactly the signal a scheduler wants.

Designing for deferral#

The design question is: what is the minimum useful work, and when is it valid?

  • A message sync: valid when there is network and the app is not rate-limited. WorkManager with a network constraint.
  • A photo backup: valid when on Wi-Fi and charging, and it can take hours. A unique work chain with a battery-not-low constraint.
  • A reminder: valid at a specific time, and the user will notice if it is late. An exact alarm with the permission.
  • A periodic health check: valid eventually, never urgent. A periodic job with a generous interval, and a tolerance for being late.

The taxonomy maps directly onto the APIs, and an app that picks the wrong one will spend a long time fighting the platform.

Why maintenance windows are not a schedule#

The interval widens with idle time, which means the time of the next window is a function of how long the device has been idle — not of wall-clock time.

An app cannot compute it. It can only observe that its job ran late, and adapt: cache more aggressively, tolerate stale data, or use a different mechanism.

What survives Doze#

MechanismIn Doze
High-priority FCMdelivered
setExactAndAllowWhileIdle with permissionfires
setAndAllowWhileIdlefires, rate-limited
Ordinary alarmdeferred
JobScheduler jobdeferred to a window
Network requestblocked
Wakelockignored

The table is the design space. Everything an app needs to do in the background must map onto one of the rows that survive, or onto a maintenance window.

App Standby is not Doze for one app#

It is easy to conflate them because both defer work. The difference is the trigger: Doze is about the device being idle, App Standby is about the app being unused.

A device can be in active use with an app in the rare bucket, and an app can be active on a device in Doze. The two restrictions multiply.

The diagnostic commands#

dumpsys deviceidle shows the Doze state and the time to the next window. am set-standby-bucket forces a bucket. dumpsys jobscheduler shows each job's constraints and when it last ran.

Together they answer the only question that matters when background work does not happen: was it deferred, blocked, or never scheduled?

How it evolved#

API 1–API 22No throttlingapps wake the device freelyAPI 1
API 23 →Doze and Standbydevice-level and app-level throttlesAPI 23
API 24 →Doze on the gorestrictions apply while movingAPI 24
API 28 →Bucketsfive tiers with different quotasAPI 28
Background and power changes12 releases
APIVersionWhat changed
11.0
  • The original android.* SDK: Activity, Service, BroadcastReceiver, ContentProvider
174.2 Jelly Bean
  • Daydream service
194.4 KitKat
  • AlarmManager becomes inexact by default for targeting apps
215.0 Lollipop
  • JobScheduler
  • Implicit service intents restricted for targeting apps
268.0 Oreo
  • Background service limits
289 Pie
  • Foreground service permission
2910
  • Background activity starts restricted
3011
  • Background location requires a second request
3112
  • Exact alarms need SCHEDULE_EXACT_ALARM
  • Foreground service launch restrictions from the background
3414 Upside Down Cake
  • Foreground service types required when targeting 34
  • User-initiated data transfer jobs
3515 Vanilla Ice Cream
  • Deprecated APIs: WindowInsetsController hiding, elevate the importance of foreground services
3616 Baklava
  • JobScheduler quota changes

Under the hood#

Curiosity corner#

  • Doze was announced with a "bathtub" diagram — a phone on a table — because the original condition required the device to be completely still.
  • Maintenance windows are not synchronized across apps. Two apps' jobs may run in the same window or in different ones, and neither can rely on ordering.
  • The standby bucket is visible to the app. UsageStatsManager.getAppStandbyBucket() lets an app adjust its behaviour — for example, syncing less when it is rare.

Try it#

Force Doze
adb shell dumpsys deviceidle force-idle
Example output
Now forced in to deep idle mode
Check the current Doze state
adb shell dumpsys deviceidle | grep -i "mState|mLightState"
Example output
mState=IDLE
mLightState=ACTIVE
Set an app's bucket
adb shell am set-standby-bucket com.example.app rare
Example output
Success

Check yourself#

Check yourself

1 / 12

Why can clearing apps from the recents screen fail to save battery?

0 answered · 0 correct

Sources#

Sources

Every version number, API level, class name and date on this page is traced to one of these. Pages are re-verified against them; last_verified above says when.

  1. docsOptimize for Doze and App Standbydeveloper.android.comaccessed 2026-10-27↩
  2. docsStandby bucketsdeveloper.android.comaccessed 2026-10-27↩
  3. docsSchedule alarmsdeveloper.android.comaccessed 2026-10-27↩
  4. docsBackground tasks overview | Background workdeveloper.android.comaccessed 2026-10-27↩
  5. docsForeground services overview | Background workdeveloper.android.comaccessed 2026-10-27↩
  6. docsLow-memory killer daemon (lmkd)source.android.comaccessed 2026-10-27↩