Balancing Battery and Precision: My Journey Building a Geofencing Engine

Balancing Battery and Precision: My Journey Building a Geofencing Engine

在电池续航与精度之间寻找平衡:我构建地理围栏引擎的历程

It happened during a quiet Friday sermon at the mosque. The room was hushed, filled with the soft hum of devotion, when a piercingly loud notification chime erupted from a phone in the front row. The owner scrambled to silence it, visibly flustered, his face turning beet red as heads turned in irritation. That moment wasn’t just a nuisance; it was a profound social disruption. 事情发生在一个安静的周五清真寺礼拜中。房间里一片寂静,充满了虔诚的低语,突然,前排的一部手机发出了一声刺耳的通知铃声。机主手忙脚乱地想要将其静音,显得十分慌乱,脸涨得通红,周围的人纷纷投去不满的目光。那一刻不仅仅是一个小插曲,更是一场严重的社交干扰。

I sat there wondering why, in an era where our phones are capable of processing millions of instructions per second, they still fail at the basic task of knowing when to be quiet. That recurring friction—the ‘did I remember to silence my phone?’ anxiety—is what led me to build Muffle. We all experience it: the board meeting that gets interrupted by a ringtone, the college lecture where a notification vibration echoes across the room, or a medical appointment where you are suddenly the center of unwanted attention. 我坐在那里思考,在这个手机每秒能处理数百万条指令的时代,为什么它们连“何时该保持安静”这一基本任务都无法完成?这种反复出现的摩擦——即“我记得把手机静音了吗?”的焦虑——促使我开发了 Muffle。我们都有过这样的经历:董事会会议被铃声打断,大学课堂上通知震动声在房间里回荡,或者在看医生时突然成为众人瞩目的焦点。

Most existing solutions either rely on simple time-based schedules that fail when plans change, or they require manual intervention, which defeats the purpose of automation. I wanted a system that could detect presence at specific locations without turning the user’s phone into a paperweight by the end of the day. 大多数现有的解决方案要么依赖简单的基于时间的计划,一旦计划改变就会失效;要么需要手动干预,这违背了自动化的初衷。我想要一个系统,它能在特定地点检测到用户,同时又不会让手机在一天结束时电量耗尽变成一块“板砖”。

Architecting the geofencing engine for Muffle required balancing the inherent tension between location accuracy and battery health. On Android, you have the GeofencingClient, which is the standard API provided by Google Play Services. It is designed to handle the heavy lifting by offloading the monitoring to the system rather than keeping the GPS radio active in your own process. 为 Muffle 构建地理围栏引擎,需要在定位精度和电池健康之间平衡固有的矛盾。在 Android 上,有 Google Play 服务提供的标准 API —— GeofencingClient。它的设计初衷是通过将监控任务卸载给系统来处理繁重的工作,而不是让 GPS 模块在你的进程中持续保持活跃。

However, relying solely on this creates a “black box” problem. If a user sets a small radius, the system might not trigger the PendingIntent until they are already deep inside the location. If the radius is too large, the phone triggers false positives every time the user walks past the building on the street. 然而,仅依赖它会产生“黑盒”问题。如果用户设置的半径太小,系统可能直到用户已经深入该地点内部时才触发 PendingIntent;如果半径太大,用户每次走过街道旁的建筑物时,手机都会触发误报。

I initially tried a standard 50-meter radius for all locations. The result was disastrous. Because the device’s location provider often switches between Wi-Fi, Bluetooth, and cellular triangulation to save power, the accuracy variance was high. A user would walk into their office building, but the system wouldn’t register the transition for three or four minutes. 我最初尝试为所有地点设置 50 米的标准半径,结果是一场灾难。由于设备的位置提供程序为了省电经常在 Wi-Fi、蓝牙和蜂窝三角定位之间切换,精度偏差很大。用户走进办公楼,系统却要三四分钟后才能记录到这一位置变化。

I realized I needed a multi-layered approach. Instead of just relying on the GeofencingClient, I implemented a hybrid check. When the GeofencingClient triggers an entry event, I verify the location context with a secondary check using FusedLocationProviderClient to ensure the user is actually inside the intended boundary before changing the AudioManager state. 我意识到需要一种多层级的方法。我没有仅仅依赖 GeofencingClient,而是实现了一种混合检查机制。当 GeofencingClient 触发进入事件时,我会使用 FusedLocationProviderClient 进行二次检查来验证位置上下文,确保用户确实在预定边界内,然后再更改 AudioManager 的状态。

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

geofencingClient.addGeofences(geofencingRequest, geofencePendingIntent)
    .addOnSuccessListener { /* Log entry */ }
    .addOnFailureListener { /* Handle errors */ }

This approach allows me to keep the app in a background state using a Foreground Service, ensuring the OS doesn’t kill the process while it waits for the location broadcast. The tradeoff here is battery. By adding that extra verification step, I am forcing a temporary wake-up of the radio. However, by limiting this to only when the initial geofence is triggered, I keep the overhead minimal while ensuring the audio profile only changes when I am certain of the user’s position. 这种方法允许我使用前台服务(Foreground Service)让应用保持在后台状态,确保操作系统在等待位置广播时不会杀死该进程。这里的代价是电池消耗。通过增加额外的验证步骤,我强制无线电模块进行了短暂的唤醒。然而,通过将其限制在仅在初始地理围栏触发时执行,我将开销降到了最低,同时确保只有在确定用户位置时才更改音频配置。

What surprised me most during development was the volatility of the ACCESS_FINE_LOCATION permissions across different Android manufacturers. I assumed that if I requested the correct permissions, the OS would handle the location updates consistently. I was wrong. I spent three days debugging why my geofences weren’t firing on several Chinese-market devices, only to discover that their aggressive battery optimization policies were effectively putting my service into a deep sleep mode regardless of my Foreground Service declaration. 开发过程中最让我惊讶的是,不同 Android 厂商对 ACCESS_FINE_LOCATION 权限的处理差异巨大。我原以为只要请求了正确的权限,操作系统就会一致地处理位置更新。我错了。我花了三天时间调试为什么我的地理围栏在几款中国市场的设备上无法触发,结果发现它们激进的电池优化策略实际上将我的服务置于深度睡眠模式,完全无视了我的前台服务声明。

I had to implement a custom ‘keep-alive’ check that logs heartbeat timestamps in a local Room database. If I see a gap longer than an hour, I know the OS has restricted my ability to monitor location, and I have to push a notification to the user to check their battery settings. 我不得不实现一个自定义的“保活”检查,在本地 Room 数据库中记录心跳时间戳。如果我发现间隔超过一小时,我就知道操作系统限制了我监控位置的能力,我必须向用户推送通知,提醒他们检查电池设置。

If I were starting this project today, I would move away from relying on GPS-only triggers for the primary logic. Instead, I would implement a ‘learned’ model that weights Wi-Fi SSIDs alongside GPS coordinates. GPS is notoriously unreliable indoors, which is exactly where most people need their phones to be silent. By combining the SSID of the office router with the GPS geofence, I could achieve a much higher success rate without increasing the battery drain caused by constant polling. 如果今天重新开始这个项目,我不会再仅仅依赖 GPS 触发作为主要逻辑。相反,我会实现一个“学习型”模型,将 Wi-Fi SSID 与 GPS 坐标结合起来加权。GPS 在室内非常不可靠,而这恰恰是大多数人需要手机静音的地方。通过将办公路由器的 SSID 与 GPS 地理围栏相结合,我可以在不增加持续轮询导致的电池消耗的情况下,获得更高的成功率。

Relying on a single sensor type is a recipe for edge-case failures that drive users to uninstall. Another lesson learned the hard way was the importance of the priority system. If a user has a ‘work’ routine and a ‘prayer’ routine that overlap, the phone ends up in a conflict loop where the audio toggles rapidly between silent and vibrate. 仅依赖单一传感器类型是导致边缘情况失败的根源,这会促使用户卸载应用。另一个惨痛的教训是优先级系统的重要性。如果用户的“工作”例程和“礼拜”例程重叠,手机就会陷入冲突循环,音频在静音和震动之间快速切换。

I had to build a custom priority queue that sorts active routines by a set integer value and locks the device state to the highest priority routine until it concludes. It sounds simple on paper, but managing state transitions when a user manually overrides the volume button while a routine is active required creating a listener for AudioManager changes. You have to decide: does the user’s manual override kill the routine, or does the routine fight back? I chose to pause the routine momentarily, acknowledging that the user’s immediate intent is the only thing that truly matters in a professional environment. 我不得不构建一个自定义优先级队列,根据设定的整数值对活动例程进行排序,并将设备状态锁定在最高优先级的例程上,直到其结束。理论上听起来很简单,但在例程活动时,如果用户手动覆盖音量按钮,管理状态转换就需要创建一个 AudioManager 更改的监听器。你必须决定:用户的覆盖操作是终止例程,还是让例程“反抗”?我选择了暂时暂停例程,因为我意识到在专业环境中,用户的即时意图才是唯一真正重要的事情。

For any developer working on location-based automation, the biggest takeaway is to respect the user’s hardware. Don’t try to be too smart by over-polling. Accept that Android’s location system is an approximation, not a source of truth. Build your architecture to handle ‘fuzzy’ data. 对于任何从事基于位置自动化的开发者来说,最大的收获是尊重用户的硬件。不要试图通过过度轮询来表现得太“聪明”。要接受 Android 的定位系统只是一个近似值,而不是绝对真理。构建你的架构来处理“模糊”数据。