Haseeb

开场钩子

清真寺里一片寂静。数百人正深深地俯身礼拜,突然,一阵刺耳的合成铃声像刀子一样划破了宁静。那是我的手机。我太专注于早高峰通勤,完全忘记切换到静音模式。当数百双眼睛齐刷刷地转向我时,那股尴尬瞬间涌上心头。这不仅仅是错过了一个设置,而是我的个人纪律彻底崩盘。那一刻成了 Muffle 的催化剂。

问题

我们生活在一个高语境的世界里,手机被期望成为隐形的伙伴。无论你是在会议室、讲堂,还是礼拜场所,手机铃声响起的社交代价都不为零。Android 上现有的解决方案碎片化严重。内置的「请勿打扰」(DND)计划表虽有帮助,但它是静态的,无法应对人类很少严格遵守朝九晚五日程的现实。

我意识到,手动干预是失败的主要原因。如果我依赖记忆来静音设备,迟早会出问题。我需要一个能理解上下文的系统。我希望手机知道我是在办公室、健身房还是会议中,而不必伸手掏进口袋去切换开关。我评估过的大多数第三方应用都依赖高耗电的轮询式定位服务,四小时内就把电池耗光了。我不想要一个像 CPU 寄生虫一样的后台服务。我想要一个外科手术般的事件驱动架构,只有在地理位置真正发生变化时才唤醒。

技术决策 / 实现

为了构建 Muffle,我放弃了持续的定位轮询,转而使用 com.google.android.gms.location 包中的 GeofencingClient。核心架构决策是将繁重工作交给 Google Play Services 的融合定位提供程序,而不是自己管理 LocationManager 更新——那需要活跃的唤醒锁和持续的 GPS 轮询。我向系统注册了圆形地理围栏。

当用户定义一个位置时,我创建一个 GeofencingRequest,添加带有特定半径的 Geofence 对象。系统随后在操作系统层面监控这些边界。我的应用在后台保持空闲,直到发生转换。一旦用户跨越边界,系统就会向我的 BroadcastReceiver 广播 Intent。关键在于:只有当操作系统通知我用户进入或离开区域时,我的代码才会执行 AudioManager 转换。

kotlin
val geofence = Geofence.Builder()
.setRequestId(routine.id)
.setCircularRegion(lat, lng, radius)
.setExpirationDuration(Geofence.NEVER_EXPIRE)
.setTransitionTypes(Geofence.GEOFENCE_TRANSITION_ENTER or Geofence.GEOFENCE_TRANSITION_EXIT)
.build()

这种方法确保我不会在循环中不断计算 distanceTo() 结果。通过让融合定位提供程序处理数学运算,我将电池消耗委托给系统——它对硬件中断的优化远胜于我在 Kotlin 中编写的任何逻辑。我还实现了一个 Priority 系统。如果用户有两个重叠的地理围栏,我的本地 SQLite 数据库充当单一事实来源。BroadcastReceiver 在发出 AudioManager.setRingerMode() 命令前,会检查数据库中当前最高优先级的活跃例程。这可以防止两个地理围栏争夺音量状态的经典「闪烁」问题。

让我惊讶的地方 / 如果重来我会怎么做

真正让我感到卑微的是城市峡谷中 GPS 信号的不一致性。我最初的假设是 Geofence 触发会是瞬时的。我以为用户一踏进大楼,手机就会静音。实际上,在密集市中心——高楼大厦反射卫星信号——信号延迟意味着 ENTER 转换有时会在距离实际目标两个街区外就触发。

我惨痛地认识到,仅仅依靠 GPS 进行地理围栏在城市中注定失败。如果重新开始,我会采用混合方法,用 Wi-Fi 信号指纹来增强 Geofence API。通过扫描附近路由器的 MAC 地址,我可以比原始 GPS 坐标更可靠地验证位置转换。我还发现 AlarmManager 在不同 OEM 定制系统上的表现各不相同。一些注重激进电池管理的制造商偶尔会杀死我的后台服务,尽管我已经实现了 ForegroundService

我不得不编写特定逻辑,每当设备收到 BOOT_COMPLETED 广播时重新注册地理围栏。否则,简单的系统更新或重启就会让用户永远保持静音,因为「退出」转换永远不会触发。这里的教训是,在 Android 上,你不能指望操作系统维护你的状态。你必须将你的应用视为不断被从内存中驱逐的对象,并且必须设计具有弹性的数据持久化机制,以应对突然、无礼的终止。

实用建议

如果你在构建 Android 工具,停止思考「常驻」逻辑。最优雅的解决方案是将职责尽可能卸载给操作系统。每当你编写自定义后台循环时,你很可能错过了一个能以 10% 能耗完成相同任务的原生 API。使用系统的广播触发器。依赖 WorkManager 处理延迟任务,使用 GeofencingClient 实现位置感知功能。

不要对抗 Android 系统的电池优化;学会在其中工作。当你的应用没有出现在电池使用统计中时,用户会注意到。如果你正在为管理声音配置或自动化设备状态而苦恼,可以看看我是如何在 Muffle 中应对这些约束的。这是一个完全建立在「只在必要时才执行工作」理念上的项目,并保持用户数据仅存储在本地设备上。你可以在 https://play.google.com/store/apps/details?id=com.muffle.app 探索实现细节和这些例程背后的逻辑。为真实世界构建应用,就是承认硬件的混乱,并编写能够经受住噪声的代码。