Zygote
Memory, lifecycle & powerintermediate

Background execution limits

The decade-long campaign to stop apps running when nobody is looking: background service limits, broadcast restrictions, foreground service types, and the launch restrictions that followed.

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

Each Android release since 2016 has closed another way for an app to run when the user is not looking. The pattern is consistent: a mechanism that was reasonable when it was introduced became a battery problem when thousands of apps used it, and the platform responded by narrowing it.

TL;DR#

  • API 21Android 5.0 restricted implicit service intents; API 26Android 8.0 stopped background services and banned most implicit broadcasts.
  • API 28Android 9 required a foreground service permission; API 31Android 12 restricted starting foreground services from the background.
  • API 34Android 14 made foreground service types mandatory, with a matching permission per type.
  • API 31Android 12 required a permission for exact alarms; API 36Android 16 changed JobScheduler quota accounting.
  • The replacement for all of it is WorkManager for deferrable work, and foreground services for work the user must know about.

Mental model#

Think of a building with a generator.

Early on, any tenant could start the generator whenever they wanted. It was convenient and the building had capacity.

As the building filled, tenants started the generator for trivial reasons — a nightly report, a status check — and the fuel bill exploded. The building management responded by requiring a permit for the generator, then by limiting how long a permit lasts, then by specifying what the generator may be used for.

The permit is the foreground service. The type is what the permit says you may do. The notification is the building telling everyone the generator is running.

Where the analogy breaks#

A building can run out of fuel and everyone notices. A phone's battery drain is invisible until it is empty, and the app responsible is rarely the one the user blames. That asymmetry is why the restrictions are enforced by the platform rather than left to app judgement.

The big picture#

Loading diagram…
Text description of this diagram

The standby bucket state machine, which is one of the two throttles on background execution. As an app goes unused it moves from active to working set to frequent to rare, and each bucket tightens jobs, alarms and network. The restricted bucket removes background execution entirely. The other throttle is Doze, which applies to the whole device rather than one app.

Background limits are layered: the bucket decides how much an app may do when the device is awake, and Doze decides whether the device is awake at all. An app needs to understand both to predict when its work will run.

Deep dive#

The 2016 turning point#

API 26Android 8.0 was the release that changed the model. Two restrictions landed together:

  • Background service limits. Once an app leaves the foreground, it has a short window to start and finish work. After that, its background services are stopped.
  • Implicit broadcast restrictions. Most implicit broadcasts — those not targeted at a specific package — can no longer be received in the manifest. The app must register a receiver at runtime, while it is running.

Both restrictions had the same justification: an app that is not in the foreground should not be consuming CPU or waking the device for work the user did not ask for.1

Foreground services and their types#

A foreground service is the sanctioned way to do work that the user must know about: playing audio, tracking a workout, navigating. It shows a notification and survives background limits.

API 34Android 14 made types mandatory. A foreground service must declare a type — mediaPlayback, location, camera, microphone, dataSync, connectedDevice, and others — and hold the matching permission.2

The type is not a label. It determines:

  • Whether the service may run at all in certain situations.
  • Whether it can access the camera or microphone.
  • What the notification must say.

Launch restrictions#

API 31Android 12 restricted starting a foreground service from the background. An app that is not visible cannot simply start one; it must use an allowed exemption — an exact alarm, a high-priority FCM message, a user action, a broadcast the system considers important.

API 34Android 14 tightened it further for specific types: a background app cannot start a camera or microphone foreground service.

The effect is that "start a service and keep running" is no longer a background strategy. It is a foreground strategy that happens to continue briefly.

Exact alarms#

API 31Android 12 required SCHEDULE_EXACT_ALARM for exact alarms. API 33Android 13 introduced USE_EXACT_ALARM for apps whose core function requires it, and <Api level={34} /> tightened the default for SCHEDULE_EXACT_ALARM on some device categories.3

AlarmManager.setExactAndAllowWhileIdle is the one alarm API that fires in Doze, and it is deliberately rate-limited so it cannot be used as a general wake-up mechanism.

JobScheduler quotas#

JobScheduler is the sanctioned deferred-work mechanism: you describe constraints (network, charging, idle), and the system decides when to run the job. The bucket and Doze both affect when that happens.

API 36Android 16 changed how job execution time is counted and limited, which affects apps that run long jobs and assume a fixed quota.4 The general direction is consistent: less guaranteed time, more deferral.

WorkManager as the answer#

WorkManager is the Jetpack library that sits above JobScheduler (and alarms, on older releases). It provides:

  • A single API for deferrable guaranteed work.
  • Constraint-based scheduling.
  • Chaining and unique work.
  • Persistence across reboots.

For almost every "I need to do this later" requirement, WorkManager is the right tool, because it is the layer the system's quotas are designed around.

Broadcasts and the manifest#

The implicit broadcast restrictions changed a fundamental Android pattern. A manifest-declared receiver for CONNECTIVITY_CHANGE used to be how apps learned about network changes while not running. Now that broadcast is restricted, and the app must register at runtime or use a NetworkCallback.

The exceptions are broadcasts the system considers essential — boot completed, package replaced, and a short list of others. Everything else moved to runtime registration or to a pull-based API.

The notification permission connection#

API 33Android 13 made notifications a runtime permission. That is not a background-execution limit directly, but it changes the calculus: a foreground service without notification permission cannot show its notification, and a user who denies notifications is signalling that they do not want the app interrupting them.

How the restrictions were enforced#

The background service limit is implemented as a timer maintained by ActivityManager: when an app leaves the foreground, its background services get a window, and at the end of it they are stopped. The app is not asked to cooperate; the system stops the service.

That is a deliberate departure from Android's usual cooperative model. Memory callbacks and trim levels are advisory; the service limit is enforced.

Why implicit broadcasts were the harder change#

Services are objects the system can stop. Broadcasts are messages other apps send. Restricting an implicit broadcast means the system must decide, at delivery time, whether the receiving app is allowed to receive it — and the app may not even be running.

The result was a move from manifest-declared receivers to runtime registration, which changed the lifecycle of a whole class of features. A receiver that used to wake the app now only exists while the app is alive, which is correct for the battery and a real redesign for the app.

Foreground service types and the notification#

A foreground service must show a notification, and the type must match what the service actually does. The notification is not decorative: it is the user's ability to see and stop the work.

The types also encode permission requirements. A camera type needs camera permission; a location type needs location permission. The platform is using the type as a proxy for "the user has agreed to this kind of work".

The modern shape of a background task#

NeedMechanism
Deferrable, guaranteedWorkManager
Deferrable, best effort, periodicJobScheduler
User-visible ongoing workForeground service with a type
Precise timeExact alarm with the permission
A message from a serverHigh-priority FCM
Work while the app is visibleA coroutine on a lifecycle-aware scope

Choosing from this table is most of the design work. The API call is the easy part.

The decisions the system makes for you#

When an app calls startService on API 26 and later, the system checks whether the app is in the foreground. If it is not, the service gets a window to finish and is then stopped. The app is not asked, warned or given a chance to extend it.

The practical consequence is that "start a service and keep working" is not a strategy. The app must either complete its work inside the window or promote the service to foreground with a notification and a declared type.

Why the window exists#

The window is a compromise. Some work legitimately needs a few seconds after the app leaves the screen — finishing a write, closing a connection, committing a transaction. The window allows that without allowing indefinite background execution.

An app that uses the window for what it is for does not notice it. An app that used it as a loophole notices immediately.

How the restrictions surface in the app#

The failures are usually silent rather than exceptions:

  • A startService call returns a component name and the service never runs.
  • A manifest receiver never fires.
  • A job never runs because its constraint is never satisfied.
  • A foreground service throws when the type is missing on API 34.

The last one is the exception that is easy to diagnose; the first three are the ones that produce bug reports with no stack trace.

The migration path#

The path from a legacy background design to a compliant one is usually:

  1. Move deferrable work to WorkManager with explicit constraints.
  2. Replace manifest receivers with runtime registration where the broadcast is still allowed.
  3. Convert genuinely ongoing work to a typed foreground service.
  4. Replace polling with push where the platform provides it.

Each step reduces the chance that the app is the reason a phone's battery drains overnight.

How it evolved#

API 1–API 25Open backgroundservices and broadcasts run freelyAPI 1
API 26 →Service and broadcast limitsbackground services stop when the app leavesAPI 26
API 31 →Launch restrictionsforeground services need a reasonAPI 31
API 34 →Types mandatoryevery foreground service declares what it doesAPI 34
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#

  • The first background service limit used a timer, not a state check. An app that left the foreground had a window to finish; after it, the service was stopped even if it was doing useful work.
  • A foreground service can be killed anyway. Foreground is the highest priority a service can have, but it is not immune to the OOM killer when the device is genuinely out of memory.
  • The notification is not decoration. It is the contract: the user can see that the app is doing something and stop it.

Try it#

Active services
adb shell dumpsys activity services com.example.app
Example output
ServiceRecord{...  com.example.app/.SyncService}
Job history and quotas
adb shell dumpsys jobscheduler | grep -A5 "com.example.app"
Example output
JOB #u0a123/42: 7f3a com.example.app/.SyncJob
Required constraints: CONNECTIVITY
Satisfied constraints: CONNECTIVITY
Alarm state
adb shell dumpsys alarm | grep -A3 "com.example.app"

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. docsBackground tasks overview | Background workdeveloper.android.comaccessed 2026-10-27↩
  2. docsForeground services overview | Background workdeveloper.android.comaccessed 2026-10-27↩
  3. docsSchedule alarmsdeveloper.android.comaccessed 2026-10-27↩
  4. docsBehavior changes: Apps targeting Android 16 or higherdeveloper.android.comaccessed 2026-10-27↩
  5. docsBehavior changes: Apps targeting Android 12developer.android.comaccessed 2026-10-27↩
  6. docsOptimize for Doze and App Standbydeveloper.android.comaccessed 2026-10-27↩
  7. docsStandby bucketsdeveloper.android.comaccessed 2026-10-27↩