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.
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#
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#
| Need | Mechanism |
|---|---|
| Deferrable, guaranteed | WorkManager |
| Deferrable, best effort, periodic | JobScheduler |
| User-visible ongoing work | Foreground service with a type |
| Precise time | Exact alarm with the permission |
| A message from a server | High-priority FCM |
| Work while the app is visible | A 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
startServicecall 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:
- Move deferrable work to WorkManager with explicit constraints.
- Replace manifest receivers with runtime registration where the broadcast is still allowed.
- Convert genuinely ongoing work to a typed foreground service.
- 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 | Version | What changed |
|---|---|---|
| 1 | 1.0 |
|
| 17 | 4.2 Jelly Bean |
|
| 19 | 4.4 KitKat |
|
| 21 | 5.0 Lollipop |
|
| 26 | 8.0 Oreo |
|
| 28 | 9 Pie |
|
| 29 | 10 |
|
| 30 | 11 |
|
| 31 | 12 |
|
| 34 | 14 Upside Down Cake |
|
| 35 | 15 Vanilla Ice Cream |
|
| 36 | 16 Baklava |
|
Under the hood#
- ActiveServices.javaservice lifecycle, foreground promotion and the background time window
- BroadcastQueue.javaimplicit broadcast filtering at delivery
- Service.javastartForeground and the type parameter
- JobScheduler.javathe deferred-work API the quotas apply to
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#
adb shell dumpsys activity services com.example.appExample output
ServiceRecord{... com.example.app/.SyncService}adb shell dumpsys jobscheduler | grep -A5 "com.example.app"Example output
JOB #u0a123/42: 7f3a com.example.app/.SyncJob
Required constraints: CONNECTIVITY
Satisfied constraints: CONNECTIVITYadb shell dumpsys alarm | grep -A3 "com.example.app"Check yourself#
Check yourself
Why can clearing apps from the recents screen fail to save battery?
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.
- docsBackground tasks overview | Background workdeveloper.android.comaccessed 2026-10-27↩
- docsForeground services overview | Background workdeveloper.android.comaccessed 2026-10-27↩
- docsSchedule alarmsdeveloper.android.comaccessed 2026-10-27↩
- docsBehavior changes: Apps targeting Android 16 or higherdeveloper.android.comaccessed 2026-10-27↩
- docsBehavior changes: Apps targeting Android 12developer.android.comaccessed 2026-10-27↩
- docsOptimize for Doze and App Standbydeveloper.android.comaccessed 2026-10-27↩
- docsStandby bucketsdeveloper.android.comaccessed 2026-10-27↩