DEV Community

Haseeb
Haseeb

Posted on

Architecting reliable geofencing in Android without battery suicide

The awkward silence

It happened during a quiet afternoon in the local library. I was sitting in a study room with three other developers, and the silence was heavy. Suddenly, my phone erupted into a ringtone I thought I had disabled. It wasn't just a buzz; it was a loud, aggressive notification sound that seemed to echo off the walls. I fumbled to silence it, but the delay was enough. Everyone stared. In that moment, the productivity of the entire group evaporated, replaced by the universal, burning shame of being the person whose phone interrupted the workflow.

The friction of manual management

We live in a world of high-context environments. I need my phone silenced when I enter the office, when I’m at the mosque for prayer, or when I’m in a focused coding session. Most people rely on their memory to toggle these settings, but memory is a faulty piece of hardware. I would consistently forget to set my phone to silent before walking into a meeting, and worse, I would forget to turn the sound back on afterward. I’d spend the next three hours wondering why I wasn’t receiving any calls, only to find my phone had been vibrating silently in my pocket the entire time.

There are tools that attempt to solve this, but they often require manual intervention or massive, bloated apps that ask for every permission under the sun. I wanted something that felt like a native system feature—something that sat quietly in the background, respected the OS, and didn't turn my battery into a furnace. I realized the problem wasn't just about the sound settings; it was about the lack of an intelligent, location-aware bridge between my context and my device's state.

Implementing the Geofencing API

When I started building Muffle, I knew geofencing was the backbone. Using the GeofencingClient within the Google Play Services location library was the obvious starting point, but the implementation details are where most developers lose their way. Many assume that a simple background service polling the GPS every few seconds is the answer. That is a recipe for a battery-draining disaster that the Android system will rightfully kill within minutes.

Instead, I utilized the GeofencingRequest API, which allows the OS to handle the heavy lifting of location proximity. The key is shifting the burden of location updates to the system process rather than keeping my own application active. By registering a PendingIntent with the GeofencingClient, the system wakes up my broadcast receiver only when the user crosses a defined radius. This is far more efficient than active polling.

kotlin
val geofencingRequest = GeofencingRequest.Builder().apply {
setInitialTrigger(GeofencingRequest.INITIAL_TRIGGER_ENTER)
addGeofence(geofence)
}.build()

geofencingClient.addGeofences(geofencingRequest, geofencePendingIntent)
.addOnSuccessListener { /* Geofence added / }
.addOnFailureListener { /
Handle failure */ }

However, the tradeoff here is latency. Because the system controls the polling interval based on other apps and battery conditions, the trigger might fire a few seconds after you actually cross the geofence boundary. I had to decide: do I want real-time accuracy or battery longevity? By choosing the PRIORITY_BALANCED_POWER_ACCURACY flag, I accepted that the phone might take 5-10 seconds to detect the entry. For a sound-profile app, this is acceptable. The user would rather have a functional battery at the end of the day than have their phone go silent the exact millisecond they cross a threshold.

What surprised me

The biggest surprise wasn't the API itself, but how the Android system treats background services during Doze mode and manufacturer-specific battery optimizations. I initially assumed that a Foreground Service with a persistent notification would be enough to keep the app alive indefinitely. I was wrong. On devices from manufacturers like Xiaomi or Samsung, the OS is extremely aggressive. Even with a foreground service, the system would periodically kill my background threads if it detected high memory usage or if the user hadn't interacted with the app in a while.

I learned the hard way that you cannot fight the OS. Instead of trying to keep the app alive 24/7, I had to architect Muffle to be stateless and resilient. I moved all state management to a local Room database. If the system killed the service, the next time the location trigger fired, the app would wake up, query the database for the current location, and check the Routine status. It didn't matter if the app had been killed five minutes ago or five hours ago; the logic was tied to the event, not the uptime of the process. If I were to build it again, I would skip the complex foreground service logic entirely for the geofencing part and rely solely on WorkManager for more consistent, OS-sanctioned background task scheduling. WorkManager is much more reliable for tasks that need to survive a device reboot or a system-initiated process death.

Practical takeaways

If you are building an Android app that relies on location, stop trying to write your own location tracking logic. Use the GeofencingClient. It is designed to hand off the responsibility to the system, which is the only way to avoid battery complaints. If your app requires background activity, stop assuming your service will stay alive. Design for death. Assume your process will be destroyed by the OS at any moment and store your state in a local database so you can pick up exactly where you left off when the next trigger arrives.

Don't fight the platform. Work with the constraints that Google and device manufacturers have put in place to protect the user's battery. If you find yourself writing code that feels like a hack to keep your process alive, you are likely working against the architecture, not with it. Keep your logic decoupled from the UI and focus on event-driven architecture. For those interested in seeing how this handles real-world routines without burning through battery, you can examine the logic in Muffle at https://play.google.com/store/apps/details?id=com.muffle.app

Top comments (0)