DEV Community

Haseeb
Haseeb

Posted on

Implementing Geofencing Without Draining the Battery

The hum of the lecture hall was absolute silence, the kind that feels heavy. I was sitting in the third row, mentally rehearsing my notes, when it happened. My phone—which I had specifically checked twice—buzzed with a notification, then proceeded to play an alarm tone that sounded like a dying robot. Everyone turned. The professor paused mid-sentence. My face burned as I scrambled to power the device down, but the vibration persisted for what felt like an eternity. That moment of pure, unadulterated embarrassment was the exact second I decided the status quo of manual phone management was broken.

We live in an age where our devices are supposedly 'smart,' yet they are remarkably incapable of knowing where they are and how they should behave. Every day, people walk into meetings, mosques, churches, or libraries and forget to mute their devices. They leave, go to lunch, and remain in 'Do Not Disturb' mode for hours, missing critical calls from family or work. The existing solutions were either too heavy, requiring constant GPS polling that decimated battery life, or they relied on rudimentary time-based schedules that didn't account for the reality of human movement. I wanted a way to trigger sound profiles based on location without my users needing to carry a power bank everywhere they went.

To solve this, I dove into the GeofencingClient API provided by Google Play Services. The primary challenge wasn't just triggering a change; it was managing the radio hardware. If you blindly poll for location updates every few seconds, the OS keeps the GPS radio active, and your battery drops faster than a lead weight. I opted for the GeofencingClient because it offloads the heavy lifting to the system. Instead of the app tracking location, I register a set of circular geofences with the system, and the system wakes my app up only when a GEOFENCE_TRANSITION_ENTER or GEOFENCE_TRANSITION_EXIT event occurs. This architectural choice is crucial: it moves the power consumption from the app layer to the kernel layer.

kotlin
val geofencingRequest = GeofencingRequest.Builder().apply {
setInitialTrigger(GeofencingRequest.INITIAL_TRIGGER_ENTER)
addGeofences(geofenceList)
}.build()

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

By using a PendingIntent, I keep the app in a dormant state. The system handles the proximity detection using a combination of Wi-Fi, cellular, and GPS signals, which is far more efficient than me pinging the location provider manually. The real complexity lies in the BroadcastReceiver that captures these events. When the geofence is triggered, the OS fires the intent, my receiver wakes up, checks the current state, and updates the AudioManager to set the desired volume or mute state. This ensures the phone is only doing work exactly when it needs to, not a millisecond before.

What surprised me most during this implementation was the sheer unreliability of GPS in dense urban environments. I initially assumed that a 100-meter radius would be perfectly accurate. I was wrong. In cities with tall buildings, 'GPS drift' is a nightmare. I spent three days testing in a downtown core, only to find that my phone was toggling between 'Silent' and 'Normal' every time I walked near a skyscraper because the perceived location jumped across the street. I learned that you cannot rely on GPS alone for geofencing. To fix this, I had to implement a 'dwell time' requirement—the user must be inside the geofence for at least 30 seconds before the sound profile actually switches. It’s a simple heuristic, but it acts as a debounce filter for the erratic nature of satellite positioning.

Another assumption that fell apart was the role of the Android Foreground Service. I thought I could just let the broadcast receiver handle everything. However, modern Android versions (starting around Android 12) have very strict background execution limits. If the system is under memory pressure, it might kill my receiver process before it finishes calling the AudioManager. I had to move the actual logic into a JobIntentService to ensure the task had enough lifecycle priority to complete the mute operation reliably, even if the device was currently deep-sleeping. I would advise anyone working with location triggers to never assume your background process will survive; always design for the event to be interrupted.

If I were starting over, I would put more effort into 'fused' sensors from day one. I spent too much time trying to tweak the geofence radius when I should have been looking at ActivityRecognitionClient. Knowing whether a user is driving, walking, or sitting still changes the context of a geofence trigger. If you are driving at 60mph, triggering a 'mute phone' event because you passed a geofence boundary is annoying. If you are walking, it is perfect. Adding activity context is the next logical step for anyone looking to build truly 'smart' location triggers, as it allows you to filter out false positives based on the user's velocity.

The biggest takeaway for any developer building background tasks is to respect the system's battery optimizations rather than fighting them. Android is a complex ecosystem, and there is almost always a system-level API that does what you are trying to write from scratch, but it does it more efficiently. Don't build a custom polling loop; use the GeofencingClient. Don't try to keep a service alive indefinitely; use WorkManager or PendingIntents. The system engineers at Google have spent thousands of hours tuning these APIs to save battery, and your users will thank you if you use them correctly.

Building Muffle has been a deep dive into the constraints of modern mobile hardware. It forced me to think less about 'features' and more about 'efficiency,' which is a mindset shift that has improved my code quality across every other project I touch. If you are interested in seeing how this geofencing and routine-based logic works in practice, you can look at the implementation details here: https://play.google.com/store/apps/details?id=com.muffle.app. It is a work in progress, but it has taught me more about the Android lifecycle than any documentation page ever could.

Top comments (0)