DEV Community

Haseeb
Haseeb

Posted on

Balancing Geofencing and Battery Life in Android Development

It happened during a quiet afternoon at the community center. I was sitting in the front row, fully immersed in a lecture, when my phone vibrated against the wooden table. The sound was like a jackhammer in the silence. Everyone turned. My face burned as I scrambled to silence the device, fumbling with the volume keys while the speaker was already mid-sentence. That single moment of embarrassment was the catalyst. I realized I was tired of manually toggling sound profiles, and I was certainly tired of the human error that made me forget to unmute after leaving, missing important calls for hours afterward.

The problem

The fundamental friction lies in the disconnect between context and manual action. Humans are notoriously bad at remembering to perform context-dependent tasks, especially when those tasks require granular control over a device state like volume. We rely on muscle memory for many things, but remembering to silence a phone before entering a library, a hospital, or a place of worship requires active, conscious effort. If you fail to do it, you face social friction. If you fail to undo it, you face communication breakdown.

I looked for existing solutions, but most were either overly complex, required cloud accounts that I didn't want to manage, or hammered the battery by keeping the GPS radio active at full blast. I wanted something that felt invisible. I needed a way to trigger sound profile changes based on location, time, or events without turning the user's phone into a heat-emitting brick. The challenge wasn't just building the trigger; it was building a system that survived the aggressive background process kills that characterize modern Android versions, all while remaining power-efficient enough to run daily without the user noticing a dent in their battery stats.

The technical decision / implementation

When I started building Muffle, my first instinct was to hook into the LocationManager and track coordinates manually. This was a mistake. Polling GPS coordinates every few seconds will drain a modern smartphone battery in hours. Instead, I pivoted to the GeofencingClient within the Google Play Services library. This API is designed to offload the heavy lifting to the OS, which uses a combination of Wi-Fi, cell towers, and GPS to trigger events at the hardware level.

However, the real architectural hurdle was how to handle the PendingIntent after the OS detected a transition. When a device enters or leaves a geofenced area, the OS fires a broadcast. If your app is killed by the system, that broadcast might not be enough to reliably trigger a Service. I decided to implement a JobIntentService to handle these transitions. This allows the application to process the trigger even if the app process has been suspended.

Here is a snippet of how I define the geofencing request to ensure it balances precision with energy conservation:

kotlin
val geofence = Geofence.Builder()
.setRequestId(routineId)
.setCircularRegion(lat, lon, radiusInMeters)
.setExpirationDuration(Geofence.NEVER_EXPIRE)
.setTransitionTypes(Geofence.GEOFENCE_TRANSITION_ENTER or
Geofence.GEOFENCE_TRANSITION_EXIT)
.setNotificationResponsiveness(300000) // 5 minutes
.build()

The setNotificationResponsiveness value was my most critical configuration. By setting this to 5 minutes, I told the system that it didn't need to report the transition the exact millisecond I stepped into the zone. Allowing the OS a five-minute window to batch location updates with other system tasks significantly reduced the wake-locks, effectively lowering the battery impact of the geofencing feature. I chose this over real-time tracking because, for a sound profile manager, a delay of two minutes is an acceptable trade-off for a full day of battery life.

What surprised you / what you'd do differently

I was surprised by how aggressive Android’s battery optimization features are regarding background services. My initial assumption was that if I marked a service as a ForegroundService, it would stay alive indefinitely. I learned the hard way that on devices from manufacturers like Samsung and Xiaomi, the OS has custom task killers that will terminate even persistent foreground services if they observe unusual patterns in resource consumption.

I eventually had to implement a specific check for ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS and guide users to exclude the app from background restrictions. If I were starting over, I would have built the logic around WorkManager from day one rather than relying on a custom Service implementation. WorkManager is much better at respecting the system's scheduling constraints, and it handles device reboots much more gracefully. I spent weeks chasing bugs related to the service not restarting after a device reboot, only to realize that WorkManager would have handled the persistence layer automatically.

Another surprise was the sheer variety of ways manufacturers handle the AudioManager. Setting the volume to 'Silent' is not the same across all Android versions. On some devices, I had to account for NotificationPolicy changes in the NotificationManager, or the app would trigger the sound profile change but the phone would still vibrate or play media sounds. Navigating the Do Not Disturb access permissions proved to be more complex than any GPS or location logic I had to write.

Practical takeaway

If you are building an application that needs to stay alive in the background, stop trying to fight the Android OS. Don't build custom persistence logic if you can avoid it. Embrace WorkManager for periodic tasks and use the system-provided APIs for location and sensor data. The OS developers have spent millions of hours optimizing these services for battery life; your custom implementation, no matter how clever, will almost certainly be less efficient and more prone to being killed by the system.

Also, prioritize user transparency regarding permissions. When I added the notification explaining exactly why I needed location access in the background, my user feedback improved drastically. Users are more than happy to let an app run in the background if they understand the value exchange: they give up a tiny bit of battery power to save themselves from the embarrassment of a ringing phone in a quiet room.

Automation should solve human friction, not create new technical burdens. I built Muffle to handle these small, daily annoyances so that I could stay focused on the task at hand rather than the state of my phone. You can find more about the project and how I handle these background tasks at https://play.google.com/store/apps/details?id=com.muffle.app, where I continue to iterate on the balance between utility and battery efficiency.

Top comments (0)