Background services on Android: a constant negotiation with the OS
Why keeping a process alive in the background on Android isn't a code problem, it's about negotiating guarantees with a system designed to kill processes.
When someone without Android experience hears "we need a background service," they think of it as a code problem: write a class, register it in the manifest, done. The reality is different. On Android, a background service isn't something you implement, it's something you negotiate with an operating system whose explicit job is to kill processes that aren't on screen.
The system isn't your enemy, but it acts like one
Since Android 6 (Doze mode) onward, the system groups idle apps, restricts their network access, defers their alarms, and, under memory pressure, kills them without warning. This isn't a bug or an aggressive setting from some specific manufacturer. It's expected behavior, designed to protect battery and memory in an ecosystem with thousands of apps competing for the same limited resources.
The problem shows up when your application needs to do something the system can't easily tell apart from "background spam": listening for events, keeping a connection alive, reacting to something that happens without the user opening the app. From the system's point of view, that legitimate need is indistinguishable from a malicious app draining the battery.
The tools aren't interchangeable
Android offers several pieces to solve this, and the most common mistake is treating them as equivalent:
- A foreground service, with a persistent visible notification, is the only way to tell the system "this is intentional and the user knows about it," in exchange for a longer-lived guarantee.
- WorkManager doesn't guarantee immediate execution, it guarantees eventual execution, even if the process dies and the device reboots. It's the right tool when "at some point" is acceptable and "right now" isn't.
- BroadcastReceiver + BootReceiver let you react to system events (a signal received, a reboot) without keeping a process alive the whole time, in exchange for not controlling exactly when the code runs.
None of these pieces solve "keep my app alive." Each solves a distinct, very specific guarantee, and the correct architecture almost always combines several of them, rather than picking just one.
The real trade-off: latency vs. battery
The design decision that matters most isn't technical, it's a product decision: how fast does the system need to react, and who pays the cost of that speed?
A heartbeat every 15 seconds gives near-immediate reaction, but also battery consumption the user will notice, which can lead the system to restrict the app more aggressively. A heartbeat every few minutes saves battery, but introduces a latency window that can be unacceptable if what's expected is a real-time reaction to a time-sensitive event.
There's no universally correct answer. There's a correct answer for the specific use case, and finding it requires measuring, not guessing: what's the actual cost of reacting late? What's the actual cost, in user complaints and system restrictions, of reacting fast all the time?
The lesson that generalizes
This problem, negotiating guarantees with an environment that's actively trying to end your process, isn't unique to Android. It shows up in containers an orchestrator can kill at any moment, in serverless workers with limited execution time, in browser tabs that get unloaded from memory. The solution is never "prevent them from killing me." It's designing the system so that surviving the process's death is a normal part of how it works, not an exception.